아이디 또는 객체를 전달 하시겠습니까?


38

도메인 엔티티를 얻기 위해 비즈니스 로직 메소드를 제공 할 때 매개 변수가 오브젝트 또는 ID를 승인해야합니까? 예를 들어 다음과 같이해야합니다.

public Foo GetItem(int id) {}

아니면 이거:

public Foo GetItem(Foo foo) {}

나는 객체를 전체적으로 전달하는 것을 믿지만 객체를 얻고 ID 만 알고있는이 경우는 어떻습니까? 호출자가 빈 Foo를 작성하고 ID를 설정해야합니까, 아니면 ID를 메소드에 전달해야합니까? 들어오는 Foo는 ID를 제외하고 비어 있기 때문에 호출자가 Foo를 만들고 GetItem () 메서드로 ID를 보낼 수있을 때 ID를 설정해야한다는 이점을 보지 못합니다.

답변:


42

조회에 사용되는 단일 필드입니다.

발신자에게을 (를) 가지고 있지 않습니다 Foo. 물론, Foo다른 모든 필드를 비워두고 임시 로 만들 수 있지만 사소한 데이터 구조에서만 작동합니다. 대부분의 객체에는 대부분 비어있는 객체 접근 방식에 위배되는 불변이 있으므로 피하십시오.


감사합니다. 나는 그의 대답에서 Amiram의 # 2 포인트 로이 답변을 좋아합니다.
밥 혼

3
이것은 논리적으로 보인다. 그러나 성능면에서 나는 호출자가 객체를 가질 수 있고 그렇지 않을 수도있는 영역으로 뛰어 들었습니다. ID 만 전달하면 데이터베이스에서 해당 객체를 두 번 읽을 수 있습니다. 이것이 허용 가능한 성능 히트입니까? 또는 id 또는 객체가 모두 전달 될 가능성을 제공합니까?
computrius

나는 요즘 소금 한알로 '대상을 절대로 통과시키지 말라'는 규칙을 취합니다. 그것은 당신의 컨텍 스 / 시나리오에 달려 있습니다.
Bruno

12

현재 또는 미래에 유선으로 연결 될까요 (직렬화 / 직렬화)? who-knows-how-large 전체 오브젝트보다 단일 ID 유형을 선호하십시오.

엔터티에 대한 ID의 유형 안전성을 찾고 있다면 코드 솔루션도 있습니다. 예가 필요하면 알려주십시오.

편집 : ID의 유형 안전 확장 :

그래서, 당신의 방법을 보자 :

public Foo GetItem(int id) {}

우리는 희망 정수 것을 id전달있어 A에 대한 것입니다 Foo객체입니다. 누군가 그것을 잘못 사용하여 Bar객체의 정수 ID를 전달하거나 심지어 손으로 입력 할 수도 812341있습니다. 에 안전한 유형이 아닙니다 Foo. 둘째, 객체 Foo버전 전달을 사용해도 누군가 수정할 수 Foo있는 ID 필드가 있다고 확신 int합니다. 마지막으로 반환 유형 만 다르므로 클래스에 메서드 오버로드가 있으면 메서드 오버로드를 사용할 수 없습니다. 이 메소드를 C #에서 타입 안전하게 보이도록 다시 작성해 보겠습니다.

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

그래서 나는 IntId일반적인 부분을 가진 클래스를 소개 했습니다. 이 특별한 경우 에만 int관련 이있는 것을 원합니다 Foo. 나는 단지 나체를 통과 int하거나 IntId<Bar>실수로 그것을 할당 할 수 없습니다 . 아래는 이러한 유형 안전 식별자를 작성한 방법입니다. 실제 기본의 조작이 있음을 걸릴 노트를 수행 int입니다 데이터 액세스 계층에서. 위의 내용은 강력한 유형 만보고 내부 intID에 대한 (직접) 액세스 권한이 없습니다 . 이유가 없어야합니다.

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);
    }
}

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>) };
        }
    }
}

IntId.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}, 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;
        }
    }
}

그리고 코드베이스의 완전성을 위해 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;
        }
    }
}

그렇습니다, 그것은 철사를 넘어 가고 있습니다. 엔티티에 대한 ID의 유형 안전이 필요하다는 것을 모르겠지만 그 의미가 무엇인지 알고 싶습니다. 예, 확장 할 수 있다면 좋을 것입니다.
밥 혼

나는 그렇게했다. 약간의 코드 무거운 :)되었다
제시 C. 슬라이서

1
그건 그렇고, 나는 Origin속성을 설명하지 않았다 : 그것은 SQL Server 용어의 스키마와 매우 흡사하다. 당신은 할 수 있습니다 Foo귀하의 회계 소프트웨어에서 사용되는 그것과 다른 Foo인적 자원과 작은 필드가 데이터 액세스 계층에서 떨어져 그들에게 존재의 그. 또는 갈등이 없다면 나처럼 무시하십시오.
Jesse C. Slicer

1
@ JesseC.Slicer : 언뜻보기에 무언가를 과도하게 엔지니어링하는 완벽한 예처럼 보입니다.
Doc Brown

2
@DocBrown 은 각자 자신의 어깨 rug합니다 . 일부 사람들에게 필요한 솔루션입니다. 어떤 사람들은 그렇지 않습니다. YAGNI 인 경우 사용하지 마십시오. 필요한 경우 있습니다.
Jesse C. Slicer

5

나는 당신의 결론에 동의합니다. 몇 가지 이유로 ID를 전달하는 것이 좋습니다.

  1. 간단하다. 구성 요소 간의 인터페이스는 간단해야합니다.
  2. Fooid에 대해서만 객체를 생성한다는 것은 잘못된 값을 생성한다는 의미입니다. 누군가 실수를하여이 값을 사용할 수 있습니다.
  3. int플랫폼 전체에 걸쳐 있으며 모든 현대 언어로 기본적으로 선언 될 수 있습니다. Foo메소드 호출자에 의해 객체 를 만들려면 아마도 json 객체와 같은 복잡한 데이터 구조를 만들어야 할 것입니다.

4

벤 보이 그 (Ben Voigt)가 제안한대로 객체 식별자에 대한 조회를 설정하는 것이 현명하다고 생각합니다.

그러나 객체 식별자의 유형이 변경 될 수 있습니다. 따라서 각 항목마다 식별자 클래스를 만들고 이러한 식별자 인스턴스를 통해서만 항목을 조회 할 수 있습니다. 다음 예를 참조하십시오.

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

  public ItemId Id; { get; set; }
}

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

캡슐화를 사용했지만 Item에서 상속 할 수도 있습니다 ItemId.

이렇게하면 길을 따라 ID 유형이 변경되면 Item클래스 나 GetItem 메서드의 서명 을 변경할 필요가 없습니다 . 서비스 구현시에만 코드를 변경해야합니다 (어쨌든 모든 경우에 변경되는 유일한 것입니다)


2

방법에 따라 다릅니다 .

일반적으로의 경우 객체 Get methods를 전달 id parameter하고 다시 가져 오는 것이 상식 입니다. 업데이트하는 동안 또는 SET methods전체 객체를 설정 / 업데이트하도록 보냈습니다.

method is passing search parameters결과 집합을 검색하기 위해 (개별 기본 유형의 모음으로) 다른 경우에는 use a container to hold검색 매개 변수 가 현명 할 수 있습니다 . 장기적으로 많은 매개 변수가 변경되는 경우에 유용합니다. 따라서을 would not need변경해야합니다 signature of your method, add or remove parameter in all over the places.

당사 사이트를 사용함과 동시에 당사의 쿠키 정책개인정보 보호정책을 읽고 이해하였음을 인정하는 것으로 간주합니다.
Licensed under cc by-sa 3.0 with attribution required.