ผ่าน ID หรือวัตถุ?


38

เมื่อระบุวิธีตรรกะทางธุรกิจเพื่อรับเอนทิตีโดเมนพารามิเตอร์ควรยอมรับวัตถุหรือ ID หรือไม่ ตัวอย่างเช่นเราควรทำสิ่งนี้:

public Foo GetItem(int id) {}

หรือสิ่งนี้:

public Foo GetItem(Foo foo) {}

ฉันเชื่อในการส่งวัตถุไปรอบ ๆ อย่างครบถ้วน แต่สิ่งที่เกี่ยวกับกรณีนี้ที่เราได้รับวัตถุและเรารู้ ID เท่านั้น ผู้โทรควรสร้าง Foo ที่ว่างเปล่าและตั้ง ID หรือควรส่ง ID ไปที่เมธอด? เนื่องจาก Foo ที่เข้ามาจะว่างเปล่ายกเว้น ID ฉันไม่เห็นประโยชน์ของผู้โทรที่ต้องสร้าง Foo และตั้ง ID เมื่อมันสามารถส่ง ID ไปยังวิธี GetItem ()

คำตอบ:


42

เพียงแค่ฟิลด์เดียวที่ใช้สำหรับการค้นหา

ผู้โทรไม่มี a Fooพยายามรับสาย แน่นอนว่าคุณสามารถสร้างชั่วคราวFooด้วยฟิลด์อื่นทั้งหมดที่เว้นว่างไว้ แต่ใช้ได้กับโครงสร้างข้อมูลเล็กน้อยเท่านั้น วัตถุส่วนใหญ่มีค่าคงที่ที่จะถูกละเมิดโดยวิธีการส่วนใหญ่ที่ว่างเปล่าดังนั้นควรหลีกเลี่ยง


ขอขอบคุณ. ฉันชอบคำตอบนี้กับจุดที่ # 2 ของ Amiram ในคำตอบของเขา
Bob Horn

3
ดูเหมือนว่ามีเหตุผล แต่ประสิทธิภาพการทำงานที่ชาญฉลาดฉันได้ทำงานในพื้นที่ที่ผู้โทรอาจมีวัตถุและอาจไม่ การส่งผ่าน id เท่านั้นอาจทำให้วัตถุดังกล่าวถูกอ่านจากฐานข้อมูลสองครั้ง นี่เป็นเพียงแค่ประสิทธิภาพที่ได้รับการยอมรับหรือไม่ หรือคุณให้ความเป็นไปได้ของทั้งรหัสหรือวัตถุที่จะส่งผ่าน?
คอมพิวเตอร์

ฉันใช้กฎเหล่านี้ 'ไม่ผ่านวัตถุ' ด้วยเม็ดเกลือวันนี้ มันขึ้นอยู่กับบริบท / สถานการณ์ของคุณ
Bruno

12

สิ่งนี้จะผ่านสาย (ต่อเนื่อง / deserialized) ตลอดเวลาตอนนี้หรือในอนาคต? ชอบประเภท ID เดียวเหนือวัตถุเต็มขนาดใหญ่ใครจะรู้

หากคุณกำลังมองหาการรักษาความปลอดภัยของ ID ไปยังเอนทิตีของมันแสดงว่ามีการแก้ปัญหาด้วยรหัสเช่นกัน แจ้งให้เราทราบหากคุณต้องการตัวอย่าง

แก้ไข: การขยายประเภทความปลอดภัยของ ID:

ดังนั้นให้ใช้วิธีการของคุณ:

public Foo GetItem(int id) {}

เราหวังเพียงว่าจำนวนเต็มที่idส่งผ่านเป็นของFooวัตถุเท่านั้น ใครบางคนอาจจะผิดมันและผ่านในบางBarID 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;
        }
    }
}

และเพื่อความสมบูรณ์ของ codebase ของฉันฉันก็เขียนหนึ่งสำหรับหน่วยงาน 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 ให้กับเอนทิตี้ของ แต่ฉันสนใจที่จะดูว่าคุณหมายถึงอะไร ดังนั้นถ้าคุณสามารถขยายในนั้นที่จะดี
Bob Horn

ฉันทำไปแล้ว กลายเป็นเล็ก ๆ น้อย ๆ รหัสหนัก :)
เจสซีเครื่องตัด

1
โดยวิธีการที่ฉันไม่ได้อธิบายOriginคุณสมบัติ: มันเป็นเหมือน schema ใน SQL Server parlance คุณอาจมีโปรแกรมFooที่ใช้ในซอฟต์แวร์การบัญชีของคุณและอีกอย่างFooสำหรับทรัพยากรบุคคลและมีฟิลด์เล็ก ๆ อยู่เพื่อบอกให้พวกเขาแยกออกจากกันในชั้นการเข้าถึงข้อมูลของคุณ หรือถ้าคุณไม่มีความขัดแย้งก็อย่าเพิกเฉยเหมือนฉัน
เจสซีซี. สไลเดอร์

1
@ JesseC.Slicer: จากภาพรวมครั้งแรกดูเหมือนว่าจะเป็นตัวอย่างที่สมบูรณ์แบบสำหรับงานวิศวกรรม
Doc Brown

2
@DocBrown ยักให้แต่ละคนนั้นเอง มันเป็นทางออกที่บางคนต้องการ บางคนทำไม่ได้ ถ้า YAGNI อย่าใช้มัน หากคุณต้องการมันมี
เจสซีซี. สไลเดอร์

5

แน่นอนฉันเห็นด้วยกับข้อสรุปของคุณ ผ่านรหัสเป็นที่ต้องการด้วยเหตุผลบางอย่าง:

  1. มันง่าย อินเตอร์เฟซระหว่างส่วนประกอบควรง่าย
  2. การสร้างFooวัตถุเพียงแค่สำหรับ id หมายถึงการสร้างค่าเท็จ มีคนทำผิดและใช้ค่าเหล่านี้
  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);
}

ผมใช้ห่อหุ้ม แต่คุณยังสามารถทำให้การสืบทอดมาจากItemItemId

ด้วยวิธีนี้ถ้าชนิดของ id ของคุณเปลี่ยนไปตามถนนคุณไม่จำเป็นต้องเปลี่ยนแปลงอะไรในItemชั้นเรียนหรือในลายเซ็นต์ของวิธีการ GetItem เฉพาะในการใช้งานบริการที่คุณจะต้องเปลี่ยนรหัสของคุณ (ซึ่งเป็นสิ่งเดียวที่เปลี่ยนแปลงในทุกกรณีอยู่แล้ว)


2

มันขึ้นอยู่กับวิธีการของคุณ

โดยทั่วไปGet methodsแล้วมันเป็นเรื่องธรรมดาที่จะผ่านid parameterและรับสิ่งของกลับคืนมา ในขณะที่มีการอัพเดทหรือSET methodsคุณจะส่งวัตถุทั้งหมดไปตั้ง / อัพเดท

ในบางกรณีที่คุณmethod is passing search parameters(เป็นกลุ่มของประเภทดั้งเดิม) เพื่อเรียกชุดผลลัพธ์อาจเป็นการดีสำหรับuse a container to holdพารามิเตอร์การค้นหาของคุณ สิ่งนี้มีประโยชน์หากจำนวนพารามิเตอร์ในระยะยาวจะเปลี่ยนไป ดังนั้นคุณจะเปลี่ยนwould not needsignature of your method, add or remove parameter in all over the places

โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.