ما هو الشيء الرائع في الأدوية الجنيسة، ولماذا نستخدمها؟

StackOverflow https://stackoverflow.com/questions/77632

  •  09-06-2019
  •  | 
  •  

سؤال

اعتقدت أنني سأقدم هذه الكرة اللينة لأي شخص يرغب في ضربها خارج الحديقة.ما هي الأدوية الجنيسة، ما هي مزايا الأدوية الجنيسة، لماذا، أين، كيف يجب أن أستخدمها؟من فضلك، يبقيه أساسيا إلى حد ما.شكرًا.

هل كانت مفيدة؟

المحلول

  • يسمح لك بكتابة التعليمات البرمجية/استخدام أساليب المكتبة الآمنة للنوع، على سبيل المثال.يُضمن أن تكون القائمة <string> قائمة من السلاسل.
  • نتيجة لاستخدام الأدوية العامة، يمكن للمترجم إجراء فحوصات وقت الترجمة على الكود من أجل سلامة النوع، على سبيل المثال.هل تحاول وضع int في قائمة السلاسل تلك؟قد يؤدي استخدام ArrayList إلى حدوث خطأ أقل شفافية في وقت التشغيل.
  • أسرع من استخدام الكائنات لأنه يتجنب الملاكمة/إلغاء التغليف (حيث يتعين على .net التحويل أنواع القيمة إلى أنواع مرجعية أو العكس) أو الإرسال من الكائنات إلى النوع المرجعي المطلوب.
  • يسمح لك بكتابة التعليمات البرمجية التي تنطبق على العديد من الأنواع التي لها نفس السلوك الأساسي، على سبيل المثال.يستخدم القاموس <string, int> نفس الكود الأساسي مثل Dictionary<DateTime, double>؛باستخدام الأدوية العامة، كان على فريق الإطار كتابة جزء واحد فقط من التعليمات البرمجية لتحقيق كلتا النتيجتين مع المزايا المذكورة أعلاه أيضًا.

نصائح أخرى

أنا حقا أكره أن أكرر نفسي.أنا أكره كتابة نفس الشيء أكثر مما يجب.لا أحب إعادة صياغة الأشياء عدة مرات مع وجود اختلافات طفيفة.

بدلاً من إنشاء:

class MyObjectList  {
   MyObject get(int index) {...}
}
class MyOtherObjectList  {
   MyOtherObject get(int index) {...}
}
class AnotherObjectList  {
   AnotherObject get(int index) {...}
}

يمكنني بناء فئة واحدة قابلة لإعادة الاستخدام ...(في حالة عدم رغبتك في استخدام المجموعة الأولية لسبب ما)

class MyList<T> {
   T get(int index) { ... }
}

أصبحت الآن أكثر كفاءة بثلاثة أضعاف ولا يتعين علي سوى الاحتفاظ بنسخة واحدة.لماذا لا تريد الاحتفاظ برمز أقل؟

وينطبق هذا أيضًا على الفئات غير المجمعة مثل Callable<T> أو أ Reference<T> التي يجب أن تتفاعل مع الطبقات الأخرى.هل تريد حقا أن تمتد Callable<T> و Future<T> وكل فئة أخرى مرتبطة بإنشاء إصدارات آمنة من النوع؟

أنا لا.

يعد عدم الحاجة إلى التلبيس أحد أكبر مزايا أدوية Java العامة, ، لأنه سيتم إجراء فحص النوع في وقت الترجمة.وهذا سوف يقلل من احتمال ClassCastExceptions والتي يمكن طرحها في وقت التشغيل، ويمكن أن تؤدي إلى تعليمات برمجية أكثر قوة.

لكن أظن أنك على علم تام بذلك.

في كل مرة أنظر فيها إلى الأدوية الجنيسة ، فإنه يعطيني صداعًا.أجد أن أفضل جزء من Java هو البساطة والحد الأدنى من بناء الجملة والوحوم الأدوية ليست بسيطة وتضيف كمية كبيرة من بناء الجملة الجديد.

في البداية، لم أر فائدة الأدوية الجنيسة أيضًا.لقد بدأت في تعلم Java من بناء الجملة 1.4 (على الرغم من أن Java 5 كان خارجًا في ذلك الوقت) وعندما واجهت الأدوية العامة، شعرت أن الأمر يحتاج إلى المزيد من التعليمات البرمجية للكتابة، ولم أفهم حقًا الفوائد.

تعمل بيئة التطوير المتكاملة (IDEs) الحديثة على تسهيل كتابة التعليمات البرمجية باستخدام الأدوية العامة.

معظم بيئات التطوير المتكاملة (IDEs) الحديثة واللائقة تتمتع بالذكاء الكافي للمساعدة في كتابة التعليمات البرمجية باستخدام الأدوية العامة، خاصة عند إكمال التعليمات البرمجية.

فيما يلي مثال على صنع Map<String, Integer> مع HashMap.الكود الذي يجب أن أكتبه هو:

Map<String, Integer> m = new HashMap<String, Integer>();

وبالفعل، هناك الكثير مما يجب كتابته فقط من أجل إنشاء شيء جديد HashMap.ومع ذلك، في الواقع، لم يكن علي سوى كتابة هذا كثيرًا قبل أن يعرف Eclipse ما أحتاج إليه:

Map<String, Integer> m = new Ha كنترول+فضاء

صحيح أنني كنت بحاجة إلى الاختيار HashMap من قائمة المرشحين، ولكن في الأساس كان IDE يعرف ما يجب إضافته، بما في ذلك الأنواع العامة.باستخدام الأدوات المناسبة، فإن استخدام الأدوية العامة ليس بالأمر السيئ للغاية.

بالإضافة إلى ذلك، نظرًا لأن الأنواع معروفة، عند استرداد العناصر من المجموعة العامة، سيعمل IDE كما لو كان هذا الكائن بالفعل كائنًا من نوعه المعلن - ليست هناك حاجة إلى البحث عن IDE لمعرفة نوع الكائن يكون.

الميزة الرئيسية للأدوية العامة تأتي من الطريقة التي تعمل بها بشكل جيد مع ميزات Java 5 الجديدة. فيما يلي مثال على رمي الأعداد الصحيحة إلى أ Set وحساب مجموعها:

Set<Integer> set = new HashSet<Integer>();
set.add(10);
set.add(42);

int total = 0;
for (int i : set) {
  total += i;
}

في هذا الجزء من التعليمات البرمجية، توجد ثلاث ميزات جديدة لـ Java 5:

أولاً، تسمح الأدوية العامة والعلبة التلقائية للأوليات بالأسطر التالية:

set.add(10);
set.add(42);

العدد الصحيح 10 تم وضعه تلقائيًا في ملف Integer بقيمة 10.(و نفس الشيء ل 42).ثم ذلك Integer يتم طرحه في Set والذي يعرف بالإمساك به Integerس.تحاول رمي أ String من شأنه أن يسبب خطأ في الترجمة.

بعد ذلك، تأخذ حلقة for-each كل العناصر الثلاثة التالية:

for (int i : set) {
  total += i;
}

لأول مرة Set تحتوي Integerيتم استخدام s في حلقة لكل حلقة.يتم الإعلان عن كل عنصر ليكون int وهذا مسموح به Integer تم فك علبته والعودة إلى البدائية int.وحقيقة حدوث عملية فتح العلبة هذه معروفة لأنه تم استخدام الأدوية العامة لتحديد وجودها Integerعقدت في Set.

يمكن أن تكون الأدوية العامة بمثابة الغراء الذي يجمع بين الميزات الجديدة المقدمة في Java 5، كما أنها تجعل البرمجة أكثر بساطة وأمانًا.وفي معظم الأوقات، تكون بيئات التطوير المتكاملة (IDEs) ذكية بما يكفي لمساعدتك في الاقتراحات الجيدة، لذلك بشكل عام، لن يتطلب الأمر الكثير من الكتابة.

وبصراحة، كما يتبين من Set على سبيل المثال، أشعر أن استخدام ميزات Java 5 يمكن أن يجعل التعليمات البرمجية أكثر إيجازًا وقوة.

تحرير - مثال بدون أدوية عامة

وفيما يلي توضيح لما سبق Set سبيل المثال دون استخدام الأدوية الجنيسة.إنه ممكن، لكنه ليس لطيفًا تمامًا:

Set set = new HashSet();
set.add(10);
set.add(42);

int total = 0;
for (Object o : set) {
  total += (Integer)o;
}

(ملحوظة:سيؤدي الكود أعلاه إلى إنشاء تحذير تحويل لم يتم التحقق منه في وقت الترجمة.)

عند استخدام مجموعات غير عامة، فإن الأنواع التي يتم إدخالها في المجموعة هي كائنات من النوع Object.ولذلك، في هذا المثال، أ Object هو ما يجري addإد في المجموعة.

set.add(10);
set.add(42);

في السطور السابقة، يتم تشغيل آلية التشغيل الآلي - البدائية int قيمة 10 و 42 يتم وضعها تلقائيًا في Integer الكائنات التي تتم إضافتها إلى Set.ومع ذلك، ضع في اعتبارك أن Integer يتم التعامل مع الكائنات كما Objects، حيث لا توجد معلومات عن النوع لمساعدة المترجم في معرفة نوع Set ينبغي أن نتوقع.

for (Object o : set) {

هذا هو الجزء الحاسم.سبب عمل حلقة for-each هو أن Set ينفذ Iterable الواجهة، والتي تقوم بإرجاع ملف Iterator مع معلومات النوع، إذا كانت موجودة.(Iterator<T>, ، إنه.)

ومع ذلك، نظرًا لعدم وجود معلومات عن النوع، فإن Set سوف يعود Iterator والتي سوف ترجع القيم في Set مثل Objects، ولهذا السبب يتم استرداد العنصر في حلقة for-each يجب يكون من النوع Object.

الآن بعد أن Object يتم استرجاعها من Set, ، فإنه يحتاج إلى أن يلقي إلى Integer يدويًا لإجراء الإضافة:

  total += (Integer)o;

هنا، يتم تنفيذ typecast من Object إلى Integer.في هذه الحالة، نعلم أن هذا سيعمل دائمًا، لكن الكتابة اليدوية تجعلني أشعر دائمًا أنها تعليمات برمجية هشة يمكن أن تتضرر إذا تم إجراء تغيير طفيف في مكان آخر.(أشعر أن كل نوع من أنواع الطباعة هو عبارة عن ملف ClassCastException أنتظر حدوثه ولكني أبتعد...)

ال Integer تم الآن فتحه في ملف int ويسمح لأداء الإضافة إلى int عامل total.

آمل أن أتمكن من توضيح أن الميزات الجديدة لـ Java 5 من الممكن استخدامها مع التعليمات البرمجية غير العامة، ولكنها ليست نظيفة ومباشرة مثل كتابة التعليمات البرمجية مع الأدوية العامة.وفي رأيي، لتحقيق الاستفادة الكاملة من الميزات الجديدة في Java 5، يجب على المرء أن يبحث في الأدوية العامة، على الأقل، إذا كان يسمح بإجراء فحوصات وقت الترجمة لمنع الطباعة غير الصالحة لرمي الاستثناءات في وقت التشغيل.

إذا كنت تبحث في قاعدة بيانات أخطاء Java قبل إصدار 1.5 مباشرةً، فستجد أخطاء أكثر بسبع مرات مع NullPointerException من ClassCastException.لذا، لا يبدو أن العثور على الأخطاء يعد ميزة رائعة، أو على الأقل الأخطاء التي تستمر بعد قليل من اختبار الدخان.

بالنسبة لي، الميزة الكبيرة للأدوية الجنيسة هي أنها توثق في الكود معلومات نوعيه مهمهإذا لم أكن أرغب في توثيق معلومات هذا النوع في التعليمات البرمجية، فسأستخدم لغة مكتوبة ديناميكيًا، أو على الأقل لغة ذات استنتاج ضمني للكتابة.

إن الاحتفاظ بمجموعات الكائنات لنفسه ليس أسلوبًا سيئًا (ولكن النمط الشائع هو تجاهل التغليف بشكل فعال).بل يعتمد على ما تفعله.من الأسهل قليلاً التحقق من تمرير المجموعات إلى "الخوارزميات" (في وقت الترجمة أو قبله) باستخدام الأدوية العامة.

الأدوية العامة في جافا تسهل تعدد الأشكال البارامترية.عن طريق معلمات النوع، يمكنك تمرير الوسائط إلى الأنواع.تماما مثل طريقة String foo(String s) نماذج لبعض السلوكيات، ليس فقط لسلسلة معينة، ولكن لأي سلسلة s, ، لذلك نوع مثل List<T> نماذج لبعض السلوكيات، ليس فقط لنوع معين، ولكن لأي نوع. List<T> يقول ان لأي نوع T, ، هناك نوع من List التي عناصرها Tس.لذا List هو في الواقع أ منشئ النوع.يأخذ نوعًا كوسيطة ويبني نوعًا آخر نتيجة لذلك.

فيما يلي بعض الأمثلة على الأنواع العامة التي أستخدمها كل يوم.أولاً، واجهة عامة مفيدة جدًا:

public interface F<A, B> {
  public B f(A a);
}

هذه الواجهة تقول ذلك لبعض النوعين، A و B, ، هناك وظيفة (تسمى f) الذي يأخذ A ويعود أ B. عند تنفيذ هذه الواجهة، A و B يمكن أن تكون أي أنواع تريدها، طالما أنك توفر وظيفة f الذي يأخذ الأول ويعيد الأخير.فيما يلي مثال على تنفيذ الواجهة:

F<Integer, String> intToString = new F<Integer, String>() {
  public String f(int i) {
    return String.valueOf(i);
  }
}

قبل الأدوية الجنيسة، تم تحقيق تعدد الأشكال عن طريق فئة فرعية باستخدام extends الكلمة الرئيسية.مع الأدوية الجنيسة، يمكننا في الواقع التخلص من التصنيف الفرعي واستخدام تعدد الأشكال البارامترية بدلاً من ذلك.على سبيل المثال، فكر في فئة ذات معلمات (عامة) تُستخدم لحساب رموز التجزئة لأي نوع.بدلاً من تجاوز Object.hashCode()، سنستخدم فئة عامة مثل هذا:

public final class Hash<A> {
  private final F<A, Integer> hashFunction;

  public Hash(final F<A, Integer> f) {
    this.hashFunction = f;
  }

  public int hash(A a) {
    return hashFunction.f(a);
  }
}

هذا أكثر مرونة بكثير من استخدام الميراث، لأننا نستطيع البقاء مع موضوع استخدام التركيب وتعدد الأشكال البارامترية دون إغلاق التسلسلات الهرمية الهشة.

بالرغم من ذلك، فإن الأدوية العامة الخاصة بـ Java ليست مثالية.يمكنك التجريد على الأنواع، لكن لا يمكنك التجريد على مُنشئات النوع، على سبيل المثال.وهذا يعني أنه يمكنك قول "لأي نوع T"، ولكن لا يمكنك قول "لأي نوع T يأخذ معلمة النوع A".

لقد كتبت مقالًا عن هذه الحدود لأدوية جافا العامة هنا.

أحد المكاسب الكبيرة للأدوية الجنيسة هو أنها تسمح لك بتجنب التصنيف الفرعي.تميل الفئات الفرعية إلى أن تؤدي إلى تسلسلات هرمية للفئات هشة يصعب توسيعها، وفئات يصعب فهمها بشكل فردي دون النظر إلى التسلسل الهرمي بأكمله.

كنا قبل الأدوية الجنيسة قد يكون لديك فئات مثل Widget ممتدة بواسطة FooWidget, BarWidget, ، و BazWidget, ، مع الأدوية العامة يمكن أن يكون لديك فئة عامة واحدة Widget<A> هذا يأخذ Foo, Bar أو Baz في منشئه ليعطيك Widget<Foo>, Widget<Bar>, ، و Widget<Baz>.

تتجنب الأدوية العامة أداء الملاكمة والفتح.في الأساس، انظر إلى ArrayList vs List<T>.كلاهما يفعلان نفس الأشياء الأساسية، لكن القائمة <T> ستكون أسرع كثيرًا لأنك لن تضطر إلى وضع مربع من/إلى الكائن.

أنا أحبها فقط لأنها توفر لك طريقة سريعة لتحديد نوع مخصص (كما أستخدمها على أي حال).

لذلك على سبيل المثال، بدلًا من تعريف بنية تتكون من سلسلة وعدد صحيح، ومن ثم الاضطرار إلى تنفيذ مجموعة كاملة من الكائنات والأساليب حول كيفية الوصول إلى مصفوفة من تلك الهياكل وما إلى ذلك، يمكنك فقط إنشاء قاموس

Dictionary<int, string> dictionary = new Dictionary<int, string>();

ويقوم المترجم/IDE ببقية المهام الثقيلة.يتيح لك القاموس على وجه الخصوص استخدام النوع الأول كمفتاح (بدون قيم متكررة).

أفضل فائدة للأدوية العامة هي إعادة استخدام الكود.لنفترض أن لديك الكثير من كائنات الأعمال، وأنك ستكتب تعليمات برمجية مشابهة جدًا لكل كيان لتنفيذ نفس الإجراءات.(I.E Linq لعمليات SQL).

باستخدام الأدوية العامة، يمكنك إنشاء فئة تكون قادرة على العمل في ضوء أي من الأنواع التي ترث من فئة أساسية معينة أو تنفذ واجهة معينة كما يلي:

public interface IEntity
{

}

public class Employee : IEntity
{
    public string FirstName { get; set; }
    public string LastName { get; set; }
    public int EmployeeID { get; set; }
}

public class Company : IEntity
{
    public string Name { get; set; }
    public string TaxID { get; set }
}

public class DataService<ENTITY, DATACONTEXT>
    where ENTITY : class, IEntity, new()
    where DATACONTEXT : DataContext, new()
{

    public void Create(List<ENTITY> entities)
    {
        using (DATACONTEXT db = new DATACONTEXT())
        {
            Table<ENTITY> table = db.GetTable<ENTITY>();

            foreach (ENTITY entity in entities)
                table.InsertOnSubmit (entity);

            db.SubmitChanges();
        }
    }
}

public class MyTest
{
    public void DoSomething()
    {
        var dataService = new DataService<Employee, MyDataContext>();
        dataService.Create(new Employee { FirstName = "Bob", LastName = "Smith", EmployeeID = 5 });
        var otherDataService = new DataService<Company, MyDataContext>();
            otherDataService.Create(new Company { Name = "ACME", TaxID = "123-111-2233" });

    }
}

لاحظ إعادة استخدام نفس الخدمة بالنظر إلى الأنواع المختلفة في طريقة DoSomething أعلاه.أنيقة حقا!

هناك العديد من الأسباب الرائعة الأخرى لاستخدام الأدوية العامة في عملك، وهذا هو المفضل لدي.

  • المجموعات المكتوبة - حتى إذا كنت لا ترغب في استخدامها، فمن المحتمل أن تضطر إلى التعامل معها من مكتبات أخرى، ومصادر أخرى.

  • الكتابة العامة في إنشاء الفصل:

    الفئة العامة foo <T> {public t get () ...

  • تجنب اختيار الممثلين - لقد كرهت دائمًا أشياء مثل

    مقارن جديد {public int compareto (object o) {if (o easteof classicareabout) ...

حيث تقوم بشكل أساسي بالتحقق من الشرط الذي يجب أن يكون موجودًا فقط لأنه يتم التعبير عن الواجهة من حيث الكائنات.

كان رد فعلي الأولي تجاه الأدوية الجنيسة مشابهًا لردك - "فوضوي جدًا، ومعقد جدًا".تجربتي هي أنه بعد استخدامها لفترة قصيرة، تعتاد عليها، ويصبح الكود بدونها أقل تحديدًا بوضوح، وأقل راحة.بصرف النظر عن ذلك، فإن بقية عالم جافا يستخدمها، لذا سيتعين عليك التعامل مع البرنامج في النهاية، أليس كذلك؟

لإعطاء مثال جيد.تخيل أن لديك فصلًا يسمى Foo

public class Foo
{
   public string Bar() { return "Bar"; }
}

مثال 1الآن تريد أن يكون لديك مجموعة من كائنات Foo.لديك خياران، LIst أو ArrayList، وكلاهما يعمل بطريقة مماثلة.

Arraylist al = new ArrayList();
List<Foo> fl = new List<Foo>();

//code to add Foos
al.Add(new Foo());
f1.Add(new Foo());

في الكود أعلاه، إذا حاولت إضافة فئة FireTruck بدلاً من Foo، فستضيفها ArrayList، لكن قائمة Foo العامة ستتسبب في طرح استثناء.

المثال الثاني.

الآن لديك قائمتين من المصفوفات وتريد استدعاء الدالة Bar() في كل منهما.نظرًا لأن hte ArrayList مليء بالكائنات، فيجب عليك إرسالها قبل أن تتمكن من استدعاء bar.ولكن نظرًا لأن القائمة العامة لـ Foo يمكن أن تحتوي فقط على Foos، فيمكنك استدعاء Bar() مباشرةً عليها.

foreach(object o in al)
{
    Foo f = (Foo)o;
    f.Bar();
}

foreach(Foo f in fl)
{
   f.Bar();
}

ألم تكتب من قبل طريقة (أو فئة) حيث لم يكن المفهوم الأساسي للطريقة/الفئة مرتبطًا بإحكام بنوع بيانات محدد من المعلمات/متغيرات المثيل (فكر في القائمة المرتبطة، وظائف الحد الأقصى/الدقيقة، البحث الثنائي ، إلخ.).

ألم ترغب أبدًا في إعادة استخدام الخوارزمية/الكود دون اللجوء إلى إعادة استخدام القص واللصق أو المساس بالكتابة القوية (على سبيل المثال.اريد List من السلاسل، وليس أ List من الأشياء أنا يأمل هي سلاسل!)؟

لهذا السبب يجب عليك يريد لاستخدام الأدوية العامة (أو شيء أفضل).

لا تنس أن الأدوية العامة لا تُستخدم فقط من خلال الفئات، بل يمكن استخدامها أيضًا من خلال الطرق.على سبيل المثال، خذ المقتطف التالي:

private <T extends Throwable> T logAndReturn(T t) {
    logThrowable(t); // some logging method that takes a Throwable
    return t;
}

إنها بسيطة، ولكن يمكن استخدامها بشكل أنيق للغاية.الشيء الجميل هو أن الطريقة تُرجع كل ما تم تقديمه لها.يساعد هذا عندما تتعامل مع الاستثناءات التي تحتاج إلى إعادة طرحها مرة أخرى إلى المتصل:

    ...
} catch (MyException e) {
    throw logAndReturn(e);
}

النقطة المهمة هي أنك لا تفقد النوع عن طريق تمريره عبر إحدى الطرق.يمكنك طرح نوع الاستثناء الصحيح بدلاً من مجرد Throwable, ، والذي سيكون كل ما يمكنك فعله بدون الأدوية الجنيسة.

هذا مجرد مثال بسيط لاستخدام واحد للطرق العامة.هناك عدد لا بأس به من الأشياء الرائعة الأخرى التي يمكنك القيام بها باستخدام الطرق العامة.الأروع، في رأيي، هو الاستدلال على النوع باستخدام الأدوية العامة.خذ المثال التالي (مأخوذ من الإصدار الثاني الفعال من Java لـ Josh Bloch):

...
Map<String, Integer> myMap = createHashMap();
...
public <K, V> Map<K, V> createHashMap() {
    return new HashMap<K, V>();
}

هذا لا يفعل الكثير، لكنه يقلل من بعض الفوضى عندما تكون الأنواع العامة طويلة (أو متداخلة؛أي. Map<String, List<String>>).

الميزة الأساسية، كما يشير ميتشل، هي الكتابة القوية دون الحاجة إلى تحديد فئات متعددة.

بهذه الطريقة يمكنك القيام بأشياء مثل:

List<SomeCustomClass> blah = new List<SomeCustomClass>();
blah[0].SomeCustomFunction();

بدون الأدوية العامة، سيتعين عليك إرسال blah[0] إلى النوع الصحيح للوصول إلى وظائفه.

يلقي jvm على أي حال ...يقوم ضمنيًا بإنشاء كود يعامل النوع العام على أنه "كائن" ويقوم بإنشاء قوالب لإنشاء مثيل مطلوب.أدوية جافا هي مجرد سكر نحوي.

أعرف أن هذا سؤال C#، ولكن الأدوية العامة تُستخدم في لغات أخرى أيضًا، واستخداماتها/أهدافها متشابهة تمامًا.

استخدام مجموعات جافا الأدوية العامة منذ جافا 1.5.لذا، المكان الجيد لاستخدامها هو عندما تقوم بإنشاء كائن يشبه المجموعة الخاصة بك.

أحد الأمثلة التي أراها في كل مكان تقريبًا هو فئة Pair، والتي تحتوي على كائنين، ولكنها تحتاج إلى التعامل مع هذين الكائنين بطريقة عامة.

class Pair<F, S> {
    public final F first;
    public final S second;

    public Pair(F f, S s)
    { 
        first = f;
        second = s;   
    }
}  

كلما استخدمت فئة الاقتران هذه، يمكنك تحديد نوع الكائنات التي تريد أن تتعامل معها وأي مشاكل في النوع ستظهر في وقت الترجمة، بدلاً من وقت التشغيل.

يمكن أيضًا تحديد حدود الأدوية العامة باستخدام الكلمات الرئيسية "super" و"extends".على سبيل المثال، إذا كنت تريد التعامل مع نوع عام ولكنك تريد التأكد من أنه يمتد إلى فئة تسمى Foo (التي تحتوي على طريقة setTitle):

public class FooManager <F extends Foo>{
    public void setTitle(F foo, String title) {
        foo.setTitle(title);
    }
}

على الرغم من أنه ليس مثيرًا للاهتمام بحد ذاته، إلا أنه من المفيد معرفة أنه عندما تتعامل مع FooManager، فإنك تعلم أنه سيتعامل مع أنواع MyClass، وأن MyClass يمتد إلى Foo.

من وثائق Sun Java، ردًا على "لماذا يجب أن أستخدم الأدوية العامة؟":

"توفر الأدوية العامة طريقة لتوصيل نوع المجموعة إلى المترجم، بحيث يمكن التحقق منها.بمجرد أن يعرف المترجم نوع عنصر المجموعة، يمكنه التحقق من أنك استخدمت المجموعة بشكل متسق ويمكنه إدراج القوالب الصحيحة على القيم المأخوذة من المجموعة...الكود الذي يستخدم الأدوية العامة أكثر وضوحًا وأمانًا.... يمكن للمترجم التحقق في وقت الترجمة من عدم انتهاك قيود النوع في وقت التشغيل [التأكيد على الألغام].نظرًا لأن البرنامج يقوم بالترجمة دون تحذيرات، يمكننا أن نؤكد على وجه اليقين أنه لن يقوم بطرح ClassCastException في وقت التشغيل.التأثير الصافي لاستخدام الأدوية الجنيسة، خاصة في البرامج الكبيرة، هو تحسين القراءة والمتانة.[التأكيد على الألغام]"

تسمح لك الأدوية العامة بإنشاء كائنات مكتوبة بقوة، ولكن لا يتعين عليك تحديد نوع معين.أعتقد أن أفضل مثال مفيد هو القائمة والفئات المماثلة.

باستخدام القائمة العامة، يمكنك الحصول على قائمة قائمة كما تريد ويمكنك دائمًا الرجوع إلى الكتابة القوية، ولا يتعين عليك التحويل أو أي شيء كما تفعل مع قائمة صفيف أو قائمة قياسية.

تتيح لك الأدوية العامة استخدام الكتابة القوية للكائنات وهياكل البيانات التي يجب أن تكون قادرة على الاحتفاظ بأي كائن.كما أنه يزيل الطباعة المملة والمكلفة عند استرداد الكائنات من الهياكل العامة (الملاكمة/الفتح).

أحد الأمثلة التي تستخدم كليهما هو القائمة المرتبطة.ما الفائدة من فئة القائمة المرتبطة إذا كان بإمكانها استخدام الكائن Foo فقط؟لتنفيذ قائمة مرتبطة يمكنها التعامل مع أي نوع من الكائنات، يجب أن تكون القائمة المرتبطة والعقد الموجودة في الفئة الداخلية للعقدة الافتراضية عامة إذا كنت تريد أن تحتوي القائمة على نوع واحد فقط من الكائنات.

إذا كانت مجموعتك تحتوي على أنواع قيم، فلن تحتاج إلى وضع الكائنات في مربع/فكها عند إدراجها في المجموعة، وبالتالي يزيد أدائك بشكل كبير.يمكن للوظائف الإضافية الرائعة مثل resharper إنشاء المزيد من التعليمات البرمجية لك، مثل حلقات foreach.

ميزة أخرى لاستخدام الأدوية العامة (خاصة مع المجموعات/القوائم) هي أنك تحصل على فحص نوع الوقت التجميعي.يعد هذا مفيدًا حقًا عند استخدام قائمة عامة بدلاً من قائمة الكائنات.

السبب الوحيد هو أنها توفر اكتب السلامة

List<Customer> custCollection = new List<Customer>;

في مقابل،

object[] custCollection = new object[] { cust1, cust2 };

كمثال بسيط.

باختصار، تتيح لك الأدوية العامة تحديد ما تنوي القيام به بدقة أكبر (كتابة أقوى).

وهذا له فوائد عديدة بالنسبة لك:

  • نظرًا لأن المترجم يعرف المزيد عما تريد القيام به، فإنه يسمح لك بحذف الكثير من عمليات الكتابة لأنه يعرف بالفعل أن الكتابة ستكون متوافقة.

  • وهذا أيضًا يزودك بتعليقات سابقة حول صحة برنامجك.الأشياء التي كانت ستفشل سابقًا في وقت التشغيل (على سبيل المثال.نظرًا لأنه لا يمكن تحويل الكائن إلى النوع المطلوب)، تفشل الآن في وقت الترجمة ويمكنك إصلاح الخطأ قبل أن يقوم قسم الاختبار الخاص بك بتقديم تقرير خطأ مشفر.

  • يمكن للمترجم إجراء المزيد من التحسينات، مثل تجنب الملاكمة، وما إلى ذلك.

هناك بعض الأشياء التي يجب إضافتها/توسيعها (من وجهة نظر .NET):

تتيح لك الأنواع العامة إنشاء فئات وواجهات قائمة على الأدوار.لقد قيل هذا بالفعل بمصطلحات أساسية أكثر، لكنني أجد أنك بدأت في تصميم التعليمات البرمجية الخاصة بك باستخدام فئات يتم تنفيذها بطريقة لا تعتمد على النوع - مما ينتج عنه تعليمات برمجية قابلة لإعادة الاستخدام بدرجة كبيرة.

يمكن للحجج العامة المتعلقة بالطرق أن تفعل الشيء نفسه، ولكنها تساعد أيضًا في تطبيق مبدأ "أخبر لا تسأل" على عملية الاختيار، أي."أعطني ما أريد، وإذا لم تستطع، أخبرني لماذا".

أستخدمها على سبيل المثال في GenericDao الذي تم تنفيذه باستخدام SpringORM وHbernate والذي يبدو هكذا

public abstract class GenericDaoHibernateImpl<T> 
    extends HibernateDaoSupport {

    private Class<T> type;

    public GenericDaoHibernateImpl(Class<T> clazz) {
        type = clazz;
    }

    public void update(T object) {
        getHibernateTemplate().update(object);
    }

    @SuppressWarnings("unchecked")
    public Integer count() {
    return ((Integer) getHibernateTemplate().execute(
        new HibernateCallback() {
            public Object doInHibernate(Session session) {
                    // Code in Hibernate for getting the count
                }
        }));
    }
  .
  .
  .
}

باستخدام الأدوية العامة، فإن تطبيقاتي لـ DAOs هذه تجبر المطور على تمريرها فقط للكيانات التي تم تصميمها من أجلها فقط عن طريق التصنيف الفرعي لـ GenericDao

public class UserDaoHibernateImpl extends GenericDaoHibernateImpl<User> {
    public UserDaoHibernateImpl() {
        super(User.class);     // This is for giving Hibernate a .class
                               // work with, as generics disappear at runtime
    }

    // Entity specific methods here
}

إطار العمل الصغير الخاص بي أكثر قوة (يحتوي على أشياء مثل التصفية والتحميل البطيء والبحث).لقد بسّطت هنا فقط لأعطيك مثالاً

أنا، مثل ستيف وأنت، قلت في البداية "فوضوي ومعقد للغاية" ولكن الآن أرى مزاياه

لقد تم بالفعل ذكر الفوائد الواضحة مثل "أمان الكتابة" و"عدم الإرسال"، لذا ربما يمكنني التحدث عن بعض "الفوائد" الأخرى التي آمل أن تكون مفيدة.

بادئ ذي بدء، الأدوية العامة هي مفهوم مستقل عن اللغة، وIMO، قد يكون الأمر أكثر منطقية إذا كنت تفكر في تعدد الأشكال العادي (وقت التشغيل) في نفس الوقت.

على سبيل المثال، تعدد الأشكال كما نعرفه من التصميم الموجه للكائنات له مفهوم وقت التشغيل حيث يتم اكتشاف كائن المتصل في وقت التشغيل أثناء تنفيذ البرنامج ويتم استدعاء الطريقة ذات الصلة وفقًا لنوع وقت التشغيل.في الأدوية العامة، الفكرة متشابهة إلى حد ما ولكن كل شيء يحدث في وقت الترجمة.ماذا يعني ذلك وكيف تستفيد منه؟

(دعونا نلتزم بالطرق العامة لإبقائها مضغوطة) وهذا يعني أنه لا يزال بإمكانك استخدام نفس الطريقة في فئات منفصلة (كما فعلت سابقًا في الفئات متعددة الأشكال) ولكن هذه المرة يتم إنشاؤها تلقائيًا بواسطة المترجم اعتمادًا على مجموعة الأنواع في وقت التجميع.يمكنك تحديد أساليبك بناءً على النوع الذي تقدمه في وقت الترجمة.لذلك، بدلا من كتابة الأساليب من الصفر لكل نوع واحد لديك كما تفعل في تعدد الأشكال في وقت التشغيل (تجاوز الطريقة)، فأنت تسمح للمترجمين بالقيام بالعمل أثناء التجميع.يتمتع هذا بميزة واضحة حيث أنك لا تحتاج إلى استنتاج جميع الأنواع الممكنة التي يمكن استخدامها في نظامك مما يجعله أكثر قابلية للتوسع دون تغيير التعليمات البرمجية.

تعمل الفصول بنفس الطريقة تقريبًا.يمكنك تحديد النوع ويتم إنشاء الكود بواسطة المترجم.

بمجرد حصولك على فكرة "وقت الترجمة"، يمكنك الاستفادة من الأنواع "المحدودة" وتقييد ما يمكن تمريره كنوع ذو معلمات من خلال الفئات/الطرق.لذلك، يمكنك التحكم في ما سيتم تمريره وهو أمر قوي خاصة أن لديك إطار عمل يستهلكه أشخاص آخرون.

public interface Foo<T extends MyObject> extends Hoo<T>{
    ...
}

لا يمكن لأحد أن يقوم بتعيين شيء آخر غير MyObject الآن.

يمكنك أيضًا "فرض" قيود الكتابة على وسيطات أسلوبك مما يعني أنه يمكنك التأكد من أن وسيطات أسلوبك تعتمد على نفس النوع.

public <T extends MyObject> foo(T t1, T t2){
    ...
}   

نأمل أن يكون كل هذا منطقيا.

لقد ألقيت محاضرة ذات مرة حول هذا الموضوع.يمكنك العثور على الشرائح والكود والتسجيل الصوتي الخاص بي على http://www.adventuresinsoftware.com/generics/.

يعد استخدام الأدوية العامة للمجموعات أمرًا بسيطًا ونظيفًا.حتى لو راهنت عليها في أي مكان آخر، فإن المكاسب من المجموعات تعتبر فوزًا بالنسبة لي.

List<Stuff> stuffList = getStuff();
for(Stuff stuff : stuffList) {
    stuff.do();
}

ضد

List stuffList = getStuff();
Iterator i = stuffList.iterator();
while(i.hasNext()) {
    Stuff stuff = (Stuff)i.next();
    stuff.do();
}

أو

List stuffList = getStuff();
for(int i = 0; i < stuffList.size(); i++) {
    Stuff stuff = (Stuff)stuffList.get(i);
    stuff.do();
}

وهذا وحده يستحق "التكلفة" الهامشية للأدوية الجنيسة، ولا يتعين عليك أن تكون خبيرًا عامًا لاستخدام هذا الدواء والحصول على القيمة.

تمنحك الأدوية العامة أيضًا القدرة على إنشاء المزيد من الكائنات/الأساليب القابلة لإعادة الاستخدام مع الاستمرار في توفير دعم خاص بالنوع.يمكنك أيضًا الحصول على الكثير من الأداء في بعض الحالات.لا أعرف المواصفات الكاملة لـ Java Generics، لكن في .NET يمكنني تحديد القيود على معلمة النوع، مثل Implements a Interface، وConstructor، وDerivation.

مرخصة بموجب: CC-BY-SA مع الإسناد
لا تنتمي إلى StackOverflow
scroll top