Đạt ID hoặc đối tượng?


38

Khi cung cấp một phương thức logic nghiệp vụ để có được một thực thể miền, tham số này có nên chấp nhận một đối tượng hoặc ID không? Ví dụ: chúng ta nên làm điều này:

public Foo GetItem(int id) {}

hoặc này:

public Foo GetItem(Foo foo) {}

Tôi tin tưởng vào việc chuyển các đối tượng xung quanh, toàn bộ, nhưng trường hợp này khi chúng ta nhận được một đối tượng và chúng ta chỉ biết ID thì sao? Người gọi nên tạo một Foo trống và đặt ID, hay chỉ nên chuyển ID cho phương thức? Vì Foo đến sẽ trống, ngoại trừ ID, tôi không thấy lợi ích của người gọi khi tạo Foo và đặt ID của nó khi có thể gửi ID đến phương thức GetItem ().

Câu trả lời:


42

Chỉ là trường duy nhất được sử dụng để tra cứu.

Người gọi không có Foo, nó đang cố lấy một cái. Chắc chắn, bạn có thể tạm thời Foovới tất cả các trường khác để trống, nhưng điều đó chỉ hoạt động đối với các cấu trúc dữ liệu tầm thường. Hầu hết các đối tượng có bất biến sẽ bị vi phạm bởi cách tiếp cận đối tượng chủ yếu là trống rỗng, vì vậy hãy tránh nó.


Cảm ơn bạn. Tôi thích câu trả lời này với điểm số 2 của Amiram trong câu trả lời của anh ấy.
Bob Horn

3
Điều này có vẻ hợp lý. Nhưng hiệu suất khôn ngoan, tôi đã chạy vào các khu vực nơi người gọi có thể có đối tượng và nó có thể không. Chỉ truyền id có thể dẫn đến việc đối tượng được đọc từ cơ sở dữ liệu hai lần. Đây có phải chỉ là một hiệu suất chấp nhận được? Hay bạn cung cấp khả năng của cả id hoặc đối tượng được thông qua?
máy tính

Tôi có những quy tắc 'không bao giờ vượt qua đối tượng' với một hạt muối ngày nay. Nó chỉ phụ thuộc vào bối cảnh / kịch bản của bạn.
Bruno

12

Điều này sẽ đi qua dây (nối tiếp / giải trừ) bất cứ lúc nào hoặc trong tương lai? Yêu thích loại ID duy nhất trên đối tượng đầy đủ người biết.

Nếu bạn đang tìm kiếm sự an toàn về loại ID cho thực thể của nó, thì cũng có các giải pháp mã. Hãy cho tôi biết nếu bạn cần một ví dụ.

Chỉnh sửa: mở rộng về loại an toàn của ID:

Vì vậy, hãy thực hiện phương pháp của bạn:

public Foo GetItem(int id) {}

Chúng tôi chỉ hy vọng rằng số nguyên idđược truyền vào là dành cho một Foođối tượng. Ai đó có thể sử dụng sai và chuyển vào BarID số nguyên của một số đối tượng hoặc thậm chí chỉ bằng cách nhập bằng tay 812341. Nó không phải là loại an toàn Foo. Thứ hai, ngay cả khi bạn đã sử dụng Foophiên bản đối tượng , tôi chắc chắn Foocó trường ID intmà ai đó có thể sửa đổi. Và cuối cùng, bạn không thể sử dụng nạp chồng phương thức nếu chúng tồn tại trong một lớp với nhau vì chỉ có kiểu trả về thay đổi. Chúng ta hãy viết lại phương thức này một chút để trông an toàn kiểu trong C #:

public Foo GetItem(IntId<Foo> id) {}

Vì vậy, tôi đã giới thiệu một lớp có tên là IntIdcó một phần chung cho nó. Trong trường hợp cụ thể này, tôi muốn intchỉ liên quan đến Foo. Tôi không thể vô tình khỏa thân intvà cũng không thể IntId<Bar>vô tình gán nó cho nó. Vì vậy, dưới đây là cách tôi đã viết các định danh an toàn loại này. Làm mất lưu ý rằng các thao tác cơ bản của thực tế intchỉ có ở lớp truy cập dữ liệu của bạn. Bất cứ điều gì ở trên chỉ nhìn thấy loại mạnh và không có quyền truy cập (trực tiếp) vào intID nội bộ của nó . Nó không có lý do để.

Giao diện IModelId.cs:

namespace GenericIdentifiers
{
    using System.Runtime.Serialization;
    using System.ServiceModel;

    /// <summary>
    /// Defines an interface for an object's unique key in order to abstract out the underlying key
    /// generation/maintenance mechanism.
    /// </summary>
    /// <typeparam name="T">The type the key is representing.</typeparam>
    [ServiceContract]
    public interface IModelId<T> where T : class
    {
        /// <summary>
        /// Gets a string representation of the domain the model originated from.
        /// </summary>
        /// <value>The origin.</value>
        [DataMember]
        string Origin
        {
            [OperationContract]get;
        }

        /// <summary>
        /// The model instance identifier for the model object that this <see cref="IModelId{T}"/> refers to.
        /// Typically, this is a database key, file name, or some other unique identifier.
        /// <typeparam name="TKeyDataType">The expected data type of the identifier.</typeparam>
        /// </summary>
        /// <typeparam name="TKeyDataType">The expected data type of the identifier.</typeparam>
        /// <returns>The unique key as the data type specified.</returns>
        [OperationContract]
        TKeyDataType GetKey<TKeyDataType>();

        /// <summary>
        /// Performs an equality check on the two model identifiers and returns <c>true</c> if they are equal; otherwise
        /// <c>false</c> is returned.  All implementations must also override the equal operator.
        /// </summary>
        /// <param name="obj">The identifier to compare against.</param>
        /// <returns><c>true</c> if the identifiers are equal; otherwise <c>false</c> is returned.</returns>
        [OperationContract]
        bool Equals(IModelId<T> obj);
    }
}

Lớp cơ sở ModelIdBase.cs:

namespace GenericIdentifiers
{
    using System;
    using System.Collections.Generic;
    using System.Runtime.Serialization;

    /// <summary>
    /// Represents an object's unique key in order to abstract out the underlying key generation/maintenance mechanism.
    /// </summary>
    /// <typeparam name="T">The type the key is representing.</typeparam>
    [DataContract(IsReference = true)]
    [KnownType("GetKnownTypes")]
    public abstract class ModelIdBase<T> : IModelId<T> where T : class
    {
        /// <summary>
        /// Gets a string representation of the domain the model originated from.
        /// </summary>
        [DataMember]
        public string Origin
        {
            get;

            internal set;
        }

        /// <summary>
        /// The model instance identifier for the model object that this <see cref="ModelIdBase{T}"/> refers to.
        /// Typically, this is a database key, file name, or some other unique identifier.
        /// </summary>
        /// <typeparam name="TKeyDataType">The expected data type of the identifier.</typeparam>
        /// <returns>The unique key as the data type specified.</returns>
        public abstract TKeyDataType GetKey<TKeyDataType>();

        /// <summary>
        /// Performs an equality check on the two model identifiers and returns <c>true</c> if they are equal;
        /// otherwise <c>false</c> is returned. All implementations must also override the equal operator.
        /// </summary>
        /// <param name="obj">The identifier to compare against.</param>
        /// <returns>
        ///   <c>true</c> if the identifiers are equal; otherwise <c>false</c> is returned.
        /// </returns>
        public abstract bool Equals(IModelId<T> obj);

        protected static IEnumerable<Type> GetKnownTypes()
        {
            return new[] { typeof(IntId<T>), typeof(GuidId<T>) };
        }
    }
}

Quốc tế:

namespace GenericIdentifiers
{
    // System namespaces
    using System;
    using System.Diagnostics;
    using System.Globalization;
    using System.Runtime.Serialization;

    /// <summary>
    /// Represents an abstraction of the database key for a Model Identifier.
    /// </summary>
    /// <typeparam name="T">The expected owner data type for this identifier.</typeparam>
    [DebuggerDisplay("Origin={Origin}, Integer Identifier={Id}")]
    [DataContract(IsReference = true)]
    public sealed class IntId<T> : ModelIdBase<T> where T : class
    {
        /// <summary>
        /// Gets or sets the unique ID.
        /// </summary>
        /// <value>The unique ID.</value>
        [DataMember]
        internal int Id
        {
            get;

            set;
        }

        /// <summary>
        /// Implements the operator ==.
        /// </summary>
        /// <param name="intIdentifier1">The first Model Identifier to compare.</param>
        /// <param name="intIdentifier2">The second Model Identifier to compare.</param>
        /// <returns>
        ///   <c>true</c> if the instances are equal; otherwise <c>false</c> is returned.
        /// </returns>
        public static bool operator ==(IntId<T> intIdentifier1, IntId<T> intIdentifier2)
        {
            return object.Equals(intIdentifier1, intIdentifier2);
        }

        /// <summary>
        /// Implements the operator !=.
        /// </summary>
        /// <param name="intIdentifier1">The first Model Identifier to compare.</param>
        /// <param name="intIdentifier2">The second Model Identifier to compare.</param>
        /// <returns>
        ///   <c>true</c> if the instances are equal; otherwise <c>false</c> is returned.
        /// </returns>
        public static bool operator !=(IntId<T> intIdentifier1, IntId<T> intIdentifier2)
        {
            return !object.Equals(intIdentifier1, intIdentifier2);
        }

        /// <summary>
        /// Performs an implicit conversion from <see cref="IntId{T}"/> to <see cref="System.Int32"/>.
        /// </summary>
        /// <param name="id">The identifier.</param>
        /// <returns>The result of the conversion.</returns>
        public static implicit operator int(IntId<T> id)
        {
            return id == null ? int.MinValue : id.GetKey<int>();
        }

        /// <summary>
        /// Performs an implicit conversion from <see cref="System.Int32"/> to <see cref="IntId{T}"/>.
        /// </summary>
        /// <param name="id">The identifier.</param>
        /// <returns>The result of the conversion.</returns>
        public static implicit operator IntId<T>(int id)
        {
            return new IntId<T> { Id = id };
        }

        /// <summary>
        /// Determines whether the specified <see cref="T:System.Object"/> is equal to the current
        /// <see cref="T:System.Object"/>.
        /// </summary>
        /// <param name="obj">The <see cref="T:System.Object"/> to compare with the current
        /// <see cref="T:System.Object"/>.</param>
        /// <returns>true if the specified <see cref="T:System.Object"/> is equal to the current
        /// <see cref="T:System.Object"/>; otherwise, false.</returns>
        /// <exception cref="T:System.NullReferenceException">The <paramref name="obj"/> parameter is null.</exception>
        public override bool Equals(object obj)
        {
            return this.Equals(obj as IModelId<T>);
        }

        /// <summary>
        /// Serves as a hash function for a particular type.
        /// </summary>
        /// <returns>
        /// A hash code for the current <see cref="T:System.Object"/>.
        /// </returns>
        public override int GetHashCode()
        {
            unchecked
            {
                var hash = 17;

                hash = (23 * hash) + (this.Origin == null ? 0 : this.Origin.GetHashCode());
                return (31 * hash) + this.GetKey<int>().GetHashCode();
            }
        }

        /// <summary>
        /// Returns a <see cref="System.String"/> that represents this instance.
        /// </summary>
        /// <returns>
        /// A <see cref="System.String"/> that represents this instance.
        /// </returns>
        public override string ToString()
        {
            return this.Origin + ":" + this.GetKey<int>().ToString(CultureInfo.InvariantCulture);
        }

        /// <summary>
        /// Performs an equality check on the two model identifiers and returns <c>true</c> if they are equal;
        /// otherwise <c>false</c> is returned.  All implementations must also override the equal operator.
        /// </summary>
        /// <param name="obj">The identifier to compare against.</param>
        /// <returns>
        ///   <c>true</c> if the identifiers are equal; otherwise <c>false</c> is returned.
        /// </returns>
        public override bool Equals(IModelId<T> obj)
        {
            if (obj == null)
            {
                return false;
            }

            return (obj.Origin == this.Origin) && (obj.GetKey<int>() == this.GetKey<int>());
        }

        /// <summary>
        /// The model instance identifier for the model object that this <see cref="ModelIdBase{T}"/> refers to.
        /// Typically, this is a database key, file name, or some other unique identifier.
        /// </summary>
        /// <typeparam name="TKeyDataType">The expected data type of the identifier.</typeparam>
        /// <returns>The unique key as the data type specified.</returns>
        public override TKeyDataType GetKey<TKeyDataType>()
        {
            return (TKeyDataType)Convert.ChangeType(this.Id, typeof(TKeyDataType), CultureInfo.InvariantCulture);
        }

        /// <summary>
        /// Generates an object from its string representation.
        /// </summary>
        /// <param name="value">The value of the model's type.</param>
        /// <returns>A new instance of this class as it's interface containing the value from the string.</returns>
        internal static ModelIdBase<T> FromString(string value)
        {
            if (value == null)
            {
                throw new ArgumentNullException("value");
            }

            int id;
            var originAndId = value.Split(new[] { ":" }, StringSplitOptions.None);

            if (originAndId.Length != 2)
            {
                throw new ArgumentOutOfRangeException("value", "value must be in the format of Origin:Identifier");
            }

            return int.TryParse(originAndId[1], NumberStyles.None, CultureInfo.InvariantCulture, out id)
                ? new IntId<T> { Id = id, Origin = originAndId[0] }
                : null;
        }
    }
}

và, vì sự hoàn hảo của codebase của tôi, tôi cũng đã viết một cái cho các thực thể GUID, GuidId.cs:

namespace GenericIdentifiers
{
    // System namespaces
    using System;
    using System.Diagnostics;
    using System.Globalization;
    using System.Runtime.Serialization;

    /// <summary>
    /// Represents an abstraction of the database key for a Model Identifier.
    /// </summary>
    /// <typeparam name="T">The expected owner data type for this identifier.</typeparam>
    [DebuggerDisplay("Origin={Origin}, GUID={Id}")]
    [DataContract(IsReference = true)]
    public sealed class GuidId<T> : ModelIdBase<T> where T : class
    {
        /// <summary>
        /// Gets or sets the unique ID.
        /// </summary>
        /// <value>The unique ID.</value>
        [DataMember]
        internal Guid Id
        {
            get;

            set;
        }

        /// <summary>
        /// Implements the operator ==.
        /// </summary>
        /// <param name="guidIdentifier1">The first Model Identifier to compare.</param>
        /// <param name="guidIdentifier2">The second Model Identifier to compare.</param>
        /// <returns>
        ///   <c>true</c> if the instances are equal; otherwise <c>false</c> is returned.
        /// </returns>
        public static bool operator ==(GuidId<T> guidIdentifier1, GuidId<T> guidIdentifier2)
        {
            return object.Equals(guidIdentifier1, guidIdentifier2);
        }

        /// <summary>
        /// Implements the operator !=.
        /// </summary>
        /// <param name="guidIdentifier1">The first Model Identifier to compare.</param>
        /// <param name="guidIdentifier2">The second Model Identifier to compare.</param>
        /// <returns>
        ///   <c>true</c> if the instances are equal; otherwise <c>false</c> is returned.
        /// </returns>
        public static bool operator !=(GuidId<T> guidIdentifier1, GuidId<T> guidIdentifier2)
        {
            return !object.Equals(guidIdentifier1, guidIdentifier2);
        }

        /// <summary>
        /// Performs an implicit conversion from <see cref="GuidId{T}"/> to <see cref="System.Guid"/>.
        /// </summary>
        /// <param name="id">The identifier.</param>
        /// <returns>The result of the conversion.</returns>
        public static implicit operator Guid(GuidId<T> id)
        {
            return id == null ? Guid.Empty : id.GetKey<Guid>();
        }

        /// <summary>
        /// Performs an implicit conversion from <see cref="System.Guid"/> to <see cref="GuidId{T}"/>.
        /// </summary>
        /// <param name="id">The identifier.</param>
        /// <returns>The result of the conversion.</returns>
        public static implicit operator GuidId<T>(Guid id)
        {
            return new GuidId<T> { Id = id };
        }

        /// <summary>
        /// Determines whether the specified <see cref="T:System.Object"/> is equal to the current
        /// <see cref="T:System.Object"/>.
        /// </summary>
        /// <param name="obj">The <see cref="T:System.Object"/> to compare with the current
        /// <see cref="T:System.Object"/>.</param>
        /// <returns>true if the specified <see cref="T:System.Object"/> is equal to the current
        /// <see cref="T:System.Object"/>; otherwise, false.</returns>
        /// <exception cref="T:System.NullReferenceException">The <paramref name="obj"/> parameter is null.</exception>
        public override bool Equals(object obj)
        {
            return this.Equals(obj as IModelId<T>);
        }

        /// <summary>
        /// Serves as a hash function for a particular type.
        /// </summary>
        /// <returns>
        /// A hash code for the current <see cref="T:System.Object"/>.
        /// </returns>
        public override int GetHashCode()
        {
            unchecked
            {
                var hash = 17;

                hash = (23 * hash) + (this.Origin == null ? 0 : this.Origin.GetHashCode());
                return (31 * hash) + this.GetKey<Guid>().GetHashCode();
            }
        }

        /// <summary>
        /// Returns a <see cref="System.String"/> that represents this instance.
        /// </summary>
        /// <returns>
        /// A <see cref="System.String"/> that represents this instance.
        /// </returns>
        public override string ToString()
        {
            return this.Origin + ":" + this.GetKey<Guid>();
        }

        /// <summary>
        /// Performs an equality check on the two model identifiers and returns <c>true</c> if they are equal;
        /// otherwise <c>false</c> is returned.  All implementations must also override the equal operator.
        /// </summary>
        /// <param name="obj">The identifier to compare against.</param>
        /// <returns>
        ///   <c>true</c> if the identifiers are equal; otherwise <c>false</c> is returned.
        /// </returns>
        public override bool Equals(IModelId<T> obj)
        {
            if (obj == null)
            {
                return false;
            }

            return (obj.Origin == this.Origin) && (obj.GetKey<Guid>() == this.GetKey<Guid>());
        }

        /// <summary>
        /// The model instance identifier for the model object that this <see cref="ModelIdBase{T}"/> refers to.
        /// Typically, this is a database key, file name, or some other unique identifier.
        /// </summary>
        /// <typeparam name="TKeyDataType">The expected data type of the identifier.</typeparam>
        /// <returns>The unique key as the data type specified.</returns>
        public override TKeyDataType GetKey<TKeyDataType>()
        {
            return (TKeyDataType)Convert.ChangeType(this.Id, typeof(TKeyDataType), CultureInfo.InvariantCulture);
        }

        /// <summary>
        /// Generates an object from its string representation.
        /// </summary>
        /// <param name="value">The value of the model's type.</param>
        /// <returns>A new instance of this class as it's interface containing the value from the string.</returns>
        internal static ModelIdBase<T> FromString(string value)
        {
            if (value == null)
            {
                throw new ArgumentNullException("value");
            }

            Guid id;
            var originAndId = value.Split(new[] { ":" }, StringSplitOptions.None);

            if (originAndId.Length != 2)
            {
                throw new ArgumentOutOfRangeException("value", "value must be in the format of Origin:Identifier");
            }

            return Guid.TryParse(originAndId[1], out id) ? new GuidId<T> { Id = id, Origin = originAndId[0] } : null;
        }
    }
}

Vâng, nó sẽ đi qua dây. Tôi không biết rằng tôi cần loại an toàn của ID đối với thực thể của nó, nhưng tôi rất muốn biết ý của bạn là gì. Vì vậy, yeah, nếu bạn có thể mở rộng về điều đó, điều đó sẽ tốt đẹp.
Bob Horn

Tôi đã làm như vậy. Trở nên hơi nặng mã :)
Jesse C. Choper

1
Nhân tiện, tôi đã không giải thích thuộc Origintính: nó rất giống một lược đồ theo cách nói của SQL Server. Bạn có thể có một Foophần mềm được sử dụng trong phần mềm Kế toán của bạn và một phần mềm khác Foodành cho Nhân sự và lĩnh vực nhỏ đó tồn tại để phân biệt chúng ở lớp truy cập dữ liệu của bạn. Hoặc, nếu bạn không có xung đột, hãy bỏ qua nó như tôi.
Jesse C. Choper

1
@ JesseC.Slicer: trong cái nhìn đầu tiên, nó trông giống như một ví dụ hoàn hảo cho kỹ thuật quá mức một cái gì đó.
Doc Brown

2
@DocBrown nhún vai mỗi người. Đó là một giải pháp mà một số người cần. Một số người thì không. Nếu YAGNI, thì đừng sử dụng nó. Nếu bạn cần nó, nó có.
Jesse C. Choper

5

Tôi chắc chắn đồng ý với kết luận của bạn. Vượt qua một id được ưa thích vì một số lý do:

  1. Nó đơn giản. giao diện giữa các thành phần nên đơn giản.
  2. Tạo một Foođối tượng chỉ cho id có nghĩa là tạo các giá trị sai. Ai đó có thể phạm sai lầm và sử dụng các giá trị này.
  3. intrộng hơn nền tảng và có thể được khai báo nguyên bản bằng tất cả các ngôn ngữ hiện đại. Để tạo một Foođối tượng bằng trình gọi phương thức, có thể bạn sẽ cần tạo một cấu trúc dữ liệu phức tạp (như đối tượng json).

4

Tôi nghĩ bạn sẽ khôn ngoan khi thiết lập việc tra cứu định danh của đối tượng như Ben Voigt đã đề xuất.

Tuy nhiên, hãy nhớ rằng loại định danh đối tượng của bạn có thể thay đổi. Như vậy, tôi sẽ tạo một lớp định danh cho từng mục của mình và chỉ cho phép tra cứu các mục thông qua các phiên bản của các định danh này. Xem ví dụ sau:

public class Item
{
  public class ItemId
  {
    public int Id { get; set;}
  }

  public ItemId Id; { get; set; }
}

public interface Service
{
  Item GetItem(ItemId id);
}

Tôi đã sử dụng đóng gói, nhưng bạn cũng có thể tạo Itemkế thừa từ ItemId.

Theo cách đó, nếu loại id của bạn thay đổi dọc đường, bạn không phải thay đổi bất cứ điều gì trong Itemlớp hoặc trong chữ ký của phương thức GetItem. Chỉ khi triển khai dịch vụ, bạn mới phải thay đổi mã của mình (đây là điều duy nhất thay đổi trong mọi trường hợp)


2

Nó phụ thuộc vào những gì phương pháp của bạn làm.

Nói chung Get methods, thông thường để vượt qua id parametervà lấy lại đối tượng. Trong khi cập nhật hoặc SET methodsbạn sẽ gửi toàn bộ đối tượng được thiết lập / cập nhật.

Trong một số trường hợp khác mà bạn method is passing search parameters(như một tập hợp các loại nguyên thủy riêng lẻ) để truy xuất một tập hợp kết quả, có thể là khôn ngoan đối với use a container to holdcác tham số tìm kiếm của bạn. Điều này rất hữu ích nếu trong thời gian dài số lượng tham số sẽ thay đổi. Vì vậy, bạn would not needđể thay đổi signature of your method, add or remove parameter in all over the places.

Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.