واجهة سلسة

في هندسة البرمجيات ، تُعرف الواجهة السلسة بأنها واجهة برمجة تطبيقات كائنية التوجه، يعتمد تصميمها بشكل كبير على تسلسل الدوال . وتهدف إلى زيادة وضوح الكود من خلال إنشاء لغة خاصة بالمجال ( DSL). وقد صاغ هذا المصطلح إريك إيفانز ومارتن فاولر عام 2005. [ 1 ]

تطبيق

تُطبَّق الواجهة السلسة عادةً من خلال تسلسل الدوال لتحقيق تتالي الدوال في اللغات التي لا تدعم التتالي بشكلٍ أصلي. ويتم ذلك عادةً بجعل كل دالة تُعيد الكائن الذي استُدعيت عليه ، والذي يُشار إليه غالبًا بـ ` return` أو `return` . وبشكلٍ أكثر تجريدًا، تحتفظ الواجهة السلسة بسياق التعليمات لاستدعاء لاحق ضمن سلسلة، حيث يكون السياق عادةً:thisself

  • يتم تحديدها من خلال القيمة المُعادة من الطريقة السابقة،
  • مرجعي ذاتي ، حيث يكون السياق الجديد مكافئًا للسياق السابق.
  • أو يتم إنهاؤها بإرجاع سياق فارغ .

لا تقتصر واجهة المستخدم السلسة على مجرد تسلسل الأساليب فحسب؛ بل تشمل أيضًا تصميم واجهة برمجة التطبيقات بحيث تُقرأ الاستدعاءات المتسلسلة كلغة خاصة بالمجال (DSL)، وغالبًا ما تتضمن تقنيات مثل الدوال المتداخلة وتحديد نطاق الكائنات بعناية. [ 1 ]

تاريخ

صِيغَ مصطلح "الواجهة السلسة" في أواخر عام 2005، مع أن هذا النمط العام للواجهات يعود إلى ابتكار تقنية تتالي الأساليب في لغة سمول توك في سبعينيات القرن الماضي، وظهور أمثلة عديدة في ثمانينياته. ومن الأمثلة الشائعة مكتبة iostream في لغة C++ ، التي تستخدم عاملَي `or` لتمرير الرسائل، وإرسال بيانات متعددة إلى الكائن نفسه، والسماح باستخدام "المُعالِجات" لاستدعاءات أساليب أخرى. ومن الأمثلة المبكرة الأخرى نظام غارنت (من عام 1988 بلغة ليسب) ونظام أموليت (من عام 1994 بلغة C++)، اللذان استخدما هذا النمط لإنشاء الكائنات وتعيين خصائصها.<<>>

أمثلة

سي شارب

تستخدم لغة C# البرمجة السلسة على نطاق واسع في LINQ لبناء الاستعلامات باستخدام "عوامل الاستعلام القياسية". ويعتمد التنفيذ على أساليب الإضافة .

باستخدام System.Collections.Generic ؛// قاموس أسماء الحيوانات من الإنجليزية إلى الفرنسية Dictionary < string , string > translations = new () { { "cat" , "chat" }, { "dog" , "chien" }, { "fish" , "poisson" }, { "bird" , "oiseau" } };// البحث عن ترجمات للكلمات الإنجليزية التي تحتوي على الحرف "a"، // مرتبة حسب الطول ومعروضة بأحرف كبيرة IEnumerable <string> query = translations.Where ( t = > t.Key.Contains ( " a " ) ) . OrderBy ( t = > t.Value.Length ) .Select ( t = > t.Value.ToUpper ( ) ) ;// نفس الاستعلام مُنشأ تدريجيًا : IEnumerable <string> filtered = translations.Where ( t = > t.Key.Contains ( " a " ) ) ; IEnumerable <string> sorted = filtered.OrderBy ( t = > t.Value.Length ) ; IEnumerable <string> finalQuery = sorted.Select ( t = > t.Value.ToUpper ( ) ) ;

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

// يُعرّف فئة سياق البيانات Context { public string FirstName { get ; set ; } public string LastName { get ; set ; } public string Sex { get ; set ; } public string Address { get ; set ; } }class Customer { private Context _context = new (); // تهيئة السياق// تعيين قيمة الخاصية public Customer FirstName ( string firstName ) { _context . FirstName = firstName ; return this ; }public Customer LastName ( string lastName ) { _context . LastName = lastName ; return this ; }public Customer Sex ( string sex ) { _context . Sex = sex ; return this ; }public Customer Address ( string address ) { _context . Address = address ; return this ; }// طباعة البيانات إلى وحدة التحكم public void Print () { Console . WriteLine ( $"الاسم الأول: {_context.FirstName} \nالاسم الأخير: {_context.LastName} \nالجنس: {_context.Sex} \nالعنوان: {_context.Address}" ); } }class Program { static void Main ( string [] args ) { // إنشاء كائن Customer c1 = new (); // استخدام تسلسل الأساليب لتعيين البيانات وطباعتها في سطر واحد c1 . FirstName ( "John" ) . LastName ( "Doe" ) . Sex ( "Male" ) . Address ( "123 Example St, Orlando FL 12345" ) . Print (); } }

يستخدم إطار اختبار .NET المسمى NUnit مزيجًا من أساليب وخصائص لغة C# بأسلوب سلس لإنشاء تأكيداته "القائمة على القيود" :

باستخدام النظام ؛Assert.That ( ( ) = > 2 * 2 , Is.AtLeast ( 3 ) .And.AtMost ( 5 ) ) ;

لغة سي++

يُعد استخدام واجهة البرمجة السلسة في لغة C++ هو iostream القياسي ، الذي يربط بين عوامل التشغيل المحملة بشكل زائد .

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

استيراد std ؛باستخدام std :: string ؛ باستخدام std :: unique_ptr ؛class Computer { private : string cpu ; string gpu ; int ram ; // القيمة الافتراضية int storage ; // القيمة الافتراضيةComputer ( string cpu , string gpu , int ram , int storage ) : cpu { std :: move ( cpu )}, gpu { std :: move ( gpu )}, ram { ram }, storage { storage } {} public : ~ Computer () = default ;void showSpecs () { std :: println ( "مواصفات الكمبيوتر:" ); std :: println ( " وحدة المعالجة المركزية: {}" , cpu ); std :: println ( " وحدة معالجة الرسومات: {}" , gpu ); std :: println ( " ذاكرة الوصول العشوائي: {} جيجابايت" , ram ); std :: println ( " مساحة التخزين: {} جيجابايت" , storage ); }class Builder { private : // يتم إعطاء المُنشئ معلمات افتراضية string cpu = "معالج مركزي عام" ; string gpu = "بطاقة رسومات مدمجة" ; int ram = 16 ; int storage = 512 ; public : Builder () = default ;Builder & ofCpu ( string cpu ) noexcept { this -> cpu = std :: move ( cpu ); return * this ; }Builder & ofGpu ( string gpu ) noexcept { this -> gpu = std :: move ( gpu ); return * this ; }Builder & withRam ( int gb ) noexcept { ram = gb ; return * this ; }Builder & withStorage ( int gb ) noexcept { storage = gb ; return * this ; }unique_ptr <Computer> build ( ) { return unique_ptr <Computer> ( new Computer ( cpu , gpu , ram , storage ) ); } } ; } ;int main () { unique_ptr < Computer > myComputer = Computer :: Builder () . ofCpu ( "AMD Ryzen 7" ) . ofGpu ( "NVIDIA RTX 4070" ) . withRam ( 32 ) . withStorage ( 1000 ) . build ();myComputer -> showSpecs (); }

جافا

مثال على توقع اختبار سلس في إطار اختبار jMock هو: [ 1 ]

mock.expects ( once ( )). method ( "m" ). with ( or ( stringContains ( "hello" ) ) , stringContains ( "howdy" )) );

تقوم مكتبة jOOQ بنمذجة لغة SQL كواجهة برمجة تطبيقات سلسة في جافا:

Author author = AUTHOR.as ( " author " ) ; create.selectFrom ( author ) .where ( exists ( selectOne ( ) . from ( BOOK ) .where ( BOOK.STATUS.eq ( BOOK_STATUS.SOLD_OUT ) ) . and ( BOOK.AUTHOR_ID.eq ( author.ID ) ) ) ) ;

يُمكّن معالج التعليقات التوضيحية fluflu من إنشاء واجهة برمجة تطبيقات سلسة باستخدام التعليقات التوضيحية في Java.

تتيح مكتبة JaQue تمثيل دوال لامدا في جافا 8 ككائنات على شكل أشجار تعبيرية في وقت التشغيل، مما يجعل من الممكن إنشاء واجهات سلسة آمنة من حيث النوع، أي بدلاً من:

Customer customer = new Customer (); customer.property ( " name " ). eq ( "John" )

يمكن للمرء أن يكتب:

method <Customer> ( customer- > customer.getName ( ) == " John " )

كما أن مكتبة اختبار الكائنات الوهمية EasyMock تستخدم هذا النمط من الواجهة على نطاق واسع لتوفير واجهة برمجة معبرة.

Collection mockCollection = EasyMock.createMock ( Collection.class ) ; EasyMock.expect ( mockCollection.remove ( null ) ) . andThrow ( new NullPointerException ( ) ) . atLeastOnce ( ) ;

في واجهة برمجة تطبيقات Java Swing، تحدد واجهة LayoutManager كيفية التحكم في موضع مكونات كائنات Container. ومن بين LayoutManagerالتطبيقات الأكثر فعالية فئة GridBagLayout، التي تتطلب استخدام هذه GridBagConstraintsالفئة لتحديد كيفية التحكم في التخطيط. مثال نموذجي على استخدام هذه الفئة هو ما يلي.

استيراد javax.swing.JLabel ؛ استيراد javax.swing.JPanel ؛ استيراد javax.swing.JTextField ؛GridBagLayout gl = new GridBagLayout ( ); JPanel p = new JPanel (); p.setLayout ( gl ) ;JLabel l = new JLabel ( "الاسم:" ); JTextField nm = new JTextField ( 10 );GridBagConstraints gc = new GridBagConstraints ( ) ; gc.gridx = 0 ; gc.gridy = 0 ; gc.fill = GridBagConstraints.NONE ; p.add ( l , gc ) ;gc.gridx = 1 ; gc.fill = GridBagConstraints.HORIZONTAL ; gc.weightx = 1 ; p.add ( nm , gc ) ;

ينتج عن هذا الكثير من التعليمات البرمجية، مما يجعل من الصعب فهم ما يحدث بالضبط. Packerتوفر الفئة آلية سلسة، لذا يمكنك كتابة ما يلي بدلاً من ذلك: [ 2 ]

استيراد javax.swing.JLabel ؛ استيراد javax.swing.JPanel ؛ استيراد javax.swing.JTextField ؛JPanel p = new JPanel (); Packer pk = new Packer ( p );JLabel l = new JLabel ( "الاسم:" ); JTextField nm = new JTextField ( 10 );pk.pack ( l ) .gridx ( 0 ) .gridy ( 0 ) ; pk.pack ( nm ) .gridx ( 1 ) .gridy ( 0 ) .fillx ( ) ;

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

جافا سكريبت/تايب سكريبت

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

// استرجاع عنصر من جدول العميل . getItem ( 'user-table' ) . setHashKey ( 'userId' , 'userA' ) . setRangeKey ( 'column' , '@' ) . execute () . then ( function ( data ) { // data.result: الكائن الناتج })

إحدى الطرق البسيطة للقيام بذلك هي استخدام وراثة النموذج الأولي، كما هو الحال في مثال TypeScriptthis هذا :

// مثال من https://schier.co/blog/2013/11/14/method-chaining-in-javascript.htmlclass Kitten { public name : string ; public color : string ;constructor () { this . name = 'Garfield' ; this . color = 'orange' ; }public setName ( name : string ) : this { this.name = name ; return this ; }public setColor ( color : string ) : this { this.color = color ; return this ; }public save ( ) : this { console.log ( ` حفظ $ { this.name } ، القطة الصغيرة $ { this.color } ` ) ; return this ; } }// استخدمه let salem = new Kitten () . setName ( 'Salem' ) . setColor ( 'black' ) . save ();

سكالا

تدعم لغة سكالا بنيةً سلسةً لكلٍ من استدعاءات الدوال ودمج الفئات ، باستخدام السمات والكلمة withالمفتاحية. على سبيل المثال:

class Color { def rgb (): Tuple3 [ Decimal ] } object Black extends Color { override def rgb (): Tuple3 [ Decimal ] = ( "0" , "0" , "0" ); }trait GUIWindow { // طرق العرض التي تُرجع هذا للرسم السلس def set_pen_color ( color : Color ): this . type def move_to ( pos : Position ): this . type def line_to ( pos : Position , end_pos : Position ): this . typedef render (): this.type = this // لا ترسم أي شيء، فقط أعد هذا الكائن، لكي تستخدمه التطبيقات الفرعية بسلاسةdef top_left (): Position def bottom_left (): Position def top_right (): Position def bottom_right (): Position }trait WindowBorder extends GUIWindow { def render (): GUIWindow = { super.render () . move_to ( top_left ()) . set_pen_color ( Black ) .line_to ( top_right ()) . line_to ( bottom_right ()) . line_to ( bottom_left ( ) ) . line_to ( top_left ( )) } }class SwingWindow extends GUIWindow { ... }val appWin = new SwingWindow ( ) with WindowBorder appWin.render ( )

راكو

في راكو ، توجد طرق عديدة، لكن من أبسطها تعريف السمات على أنها قابلة للقراءة والكتابة باستخدام الكلمة givenالمفتاحية. تُعدّ تعليقات النوع اختيارية، لكن نظام الكتابة التدريجي الأصلي يجعل الكتابة مباشرةً إلى السمات العامة أكثر أمانًا.

class Employee { subset Salary of Real where * > 0 ; subset NonEmptyString of Str where * ~~ /\S/ ; # حرف واحد على الأقل غير مسافةيحتوي على سلسلة غير فارغة $.name وهي قابلة للقراءة والكتابة ؛ يحتوي على سلسلة غير فارغة $.surname وهي قابلة للقراءة والكتابة ؛ يحتوي على راتب $.salary وهي قابلة للقراءة والكتابة ؛ method gist { return qq:to[END];  Name: $.name  Surname  Salary: $.salary  END } } my $employee = Employee . new (); معطى $employee { . name = 'Sally' ; . اسم العائلة = 'Ride' ; الراتب = 200 ؛ } قل $employee ; الناتج: الاسم: سالي اسم العائلة: رايد الراتب: 200

PHP

في لغة PHP ، يُمكن استرجاع الكائن الحالي باستخدام $thisمتغير خاص يُمثل نسخة منه. وبالتالي، return $this;تُعيد الدالة نسخة الكائن. يُعرّف المثال أدناه فئةً Employeeوثلاث دوال لتعيين اسمها ولقبها وراتبها. تُعيد كل دالة نسخة من Employeeالفئة، مما يسمح بتسلسل الدوال.

<?php declare ( strict_types = 1 );الفئة النهائية للموظف { سلسلة خاصة $name ; سلسلة خاصة $surname ; سلسلة خاصة $salary ;public function setName ( string $name ) : self { $this -> name = $name ;return $this ; }public function setSurname ( string $surname ) : self { $this -> surname = $surname ;return $this ; }public function setSalary ( string $salary ) : self { $this -> salary = $salary ;return $this ; }public function __toString () : string { return <<< INFO  Name: {$this->name}  Surname: {$this->surname}  Salary: {$this->salary} INFO ; } }# إنشاء نسخة جديدة من فئة الموظف، توم سميث، براتب 100: $employee = ( new Employee ()) -> setName ( 'Tom' ) -> setSurname ( 'Smith' ) -> setSalary ( '100' );# عرض قيمة مثيل الموظف: echo $employee ;العرض: الاسم: توم اسم العائلة: سميث الراتب: 100

بايثون

في لغة بايثون ، selfيعد الإرجاع في طريقة الكائن إحدى طرق تطبيق النمط السلس.

إلا أن مبتكر اللغة، غيدو فان روسوم، لا يشجع على استخدام هذا النمط [ 3 ] ، ولذلك يُعتبر غير متوافق مع أسلوب بايثون (غير مألوف) بالنسبة للعمليات التي لا تُرجع قيمًا جديدة. ويُقدم فان روسوم عمليات معالجة السلاسل النصية كمثال على الحالات التي يرى فيها أن النمط السلس مناسب.

class Poem : def __init__ ( self , title : str ) -> None : self . title = titleدالة indent ( self , spaces : int ) : "" " قم بإضافة مسافة بادئة للقصيدة بعدد المسافات المحدد.""" self.title = " " * spaces + self.title return selfdef suffix ( self , author : str ): " " " أضف اسم المؤلف إلى القصيدة."" " self.title = f " { self.title } - { author } " return self
>>> قصيدة ( "الطريق الذي لم يُسلك" ) . مسافة بادئة ( 4 ) . لاحقة ( "روبرت فروست" ) . عنوان ' الطريق الذي لم يُسلك - روبرت فروست'

سويفت

في Swift 3.0+، يعد إرجاع القيم selfفي الدوال إحدى طرق تطبيق النمط السلس.

صنف الشخص { متغير الاسم الأول : سلسلة نصية = "" متغير اسم العائلة : سلسلة نصية = "" متغير الاقتباس المفضل : سلسلة نصية = ""@discardableResult func set ( firstname : String ) - > Self { self.firstname = firstname return self }@discardableResult func set ( lastname : String ) - > Self { self.lastname = lastname return self }@discardableResult func set ( favoriteQuote : String ) - > Self { self.favoriteQuote = favoriteQuote return self } }
let person = Person () . set ( firstname : "John" ) . set ( lastname : "Doe" ) . set ( favoriteQuote : "I like turtles" )

ثبات

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

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

مثال جافا سكريبت

باستخدام دلالات النسخ عند الكتابة، يصبح مثال TypeScript المذكور أعلاه كالتالي:

class Kitten { public name : string ; public color : string ;constructor () { this . name = 'Garfield' ; this . color = 'orange' ; }public setName ( name : string ) : Kitten { const copy = new Kitten ( ) ; copy.color = this.color ; copy.name = name ; return copy ; }public setColor ( color : string ) : Kitten { const copy = new Kitten ( ) ; copy.name = this.name ; copy.color = color ; return copy ; }// ... }// استخدمه const kitten1 = new Kitten (). setName ( 'Salem' );const kitten2 = kitten1.setColor ( 'black ' ) ;console.log ( kitten1 , kitten2 ); // -> Kitten({ name: 'Salem', color: ' orange' }), Kitten({ name: 'Salem', color: ' black' })

مشاكل

لا يمكن رصد الأخطاء أثناء وقت الترجمة

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

تصحيح الأخطاء والإبلاغ عنها

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

استيراد java.nio.Buffer ؛ByteBuffer.allocate ( 10 ) .rewind ( ). limit ( 100 ) ;

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

استيراد java.nio.Buffer ؛ByteBuffer.allocate ( 10 ) .rewind ( ) . limit ( 100 ) ;

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

قطع الأشجار

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

استيراد java.nio.Buffer ؛ByteBuffer buffer = ByteBuffer.allocate ( 10 ) .rewind ( ). limit ( 100 ) ;

لتسجيل حالة النظام bufferبعد rewind()استدعاء الدالة، من الضروري إيقاف الاستدعاءات السلسة:

استيراد java.nio.Buffer ؛ByteBuffer buffer = ByteBuffer.allocate ( 10 ) .rewind ( ) ; System.out.println ( " First byte after rewind is " + buffer.get ( 0 ) ) ; buffer.limit ( 100 ) ;

يمكن تجاوز هذه المشكلة في اللغات التي تدعم أساليب الإضافة عن طريق تعريف إضافة جديدة لتغليف وظائف التسجيل المطلوبة، على سبيل المثال في لغة C# (باستخدام مثال Java ByteBuffer نفسه المذكور أعلاه):

باستخدام النظام ؛static class ByteBufferExtensions { public static ByteBuffer Log ( this ByteBuffer buffer , Log log , Action < ByteBuffer > getMessage ) { string message = getMessage ( buffer ); log . debug ( message ); return buffer ; } }// الاستخدام: ByteBuffer.Allocate ( 10 ) .Rewind ( ) . Log ( log , b => "First byte after rewind is " + b.Get ( 0 ) ) . Limit ( 100 ) ;

الفئات الفرعية

في اللغات ذات الكتابة القوية (مثل C++ و Java و C# وغيرها)، غالباً ما يتعين على الفئات الفرعية إعادة تعريف جميع الطرق من فئتها الأصلية التي تشارك في واجهة سلسة لتغيير نوع القيمة المُعادة. على سبيل المثال:

class A { public A foo () { // ... } }class B extends A { public B foo () { super . foo (); return this ; } // يجب تغيير نوع الإرجاع إلى B.public B bar () { // ... } } ... A a = new B (). bar (). foo (); // سيعمل هذا حتى بدون تجاوز A.foo(). B b = new B (). foo (). bar (); // سيفشل هذا إذا لم يتم تجاوز A.foo().

يمكن للغات القادرة على التعبير عن تعدد الأشكال المقيد بـ F أن تستخدمه لتجنب هذه الصعوبة. على سبيل المثال:

فئة مجردة AbstractA < T extends AbstractA <T> > { @SuppressWarnings ( " unchecked" ) public T foo () { // ... return ( T ) this ; } }class A extends AbstractA <A> { } class B extends AbstractA <B> { public B bar ( ) { // ... return this ; } }... B b = new B (). foo (). bar (); // صحيح A a = new A (). foo (); // صحيح أيضًا

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

صنف مجرد AbstractB < T يمتد من AbstractB<T>> يمتد من AbstractA <T> { @SuppressWarnings ( " unchecked " ) public T bar ( ) { // ... return ( T ) this ; } } صنف B يمتد من AbstractB <B> { }فئة مجردة AbstractC < T extends AbstractC <T> > extends AbstractB <T> { @SuppressWarnings ( " unchecked" ) public T baz () { // ... return ( T ) this ; } } class C extends AbstractC <C> { } ... C c = new C (). foo (). bar (). baz (); // صحيح B b = new B ( ). foo (). bar (); // صحيح أيضًا

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

class A { def foo (): this.type = { ... } // تُرجع this ، ودائمًا this. } class B extends A { // لا حاجة لتجاوزها! def bar (): this.type = { ... } } ... val a : A = new B ( ) . bar (). foo (); // التسلسل يعمل في كلا الاتجاهين val b : B = new B (). foo (). bar (); // وكلا سلسلتي الدوال تُنتج B

انظر أيضاً

مراجع

  1. 1 2 3 مارتن فاولر ، " واجهة سلسة "، 20 ديسمبر 2005
  2. "Interface Pack200.Packer" . أوراكل . تم الاطلاع عليه بتاريخ 13 نوفمبر 2019 .
  3. روسوم، غيدو فان (17 أكتوبر 2003). " [ Python-Dev ] قيمة الإرجاع للدالة sort()" . تم الاسترجاع في 1 فبراير 2022 .