การผกผันของการพึ่งพาเกี่ยวข้องกับฟังก์ชันลำดับสูงกว่าอย่างไร


41

วันนี้ฉันเพิ่งเห็นบทความนี้ซึ่งอธิบายถึงความเกี่ยวข้องของหลักการ SOLID ในการพัฒนา F # -

F # และหลักการออกแบบ - SOLID

และในขณะที่พูดถึงเรื่องสุดท้าย - "หลักการผกผันการพึ่งพา" ผู้เขียนกล่าวว่า:

จากมุมมองของการทำงานภาชนะและแนวคิดการฉีดเหล่านี้สามารถแก้ไขได้ด้วยฟังก์ชั่นการสั่งงานที่ง่ายขึ้นหรือรูปแบบของรูที่อยู่ตรงกลางซึ่งสร้างขึ้นเป็นภาษาโดยตรง

แต่เขาไม่ได้อธิบายเพิ่มเติม ดังนั้นคำถามของฉันคือการพึ่งพาการผกผันเกี่ยวข้องกับฟังก์ชันคำสั่งซื้อที่สูงขึ้นอย่างไร

คำตอบ:


38

การพึ่งพาการผกผันใน OOP หมายความว่าคุณใช้รหัสกับอินเทอร์เฟซซึ่งมีให้โดยการนำไปใช้ในวัตถุ

ภาษาที่รองรับฟังก์ชั่นภาษาที่สูงกว่ามักจะสามารถแก้ไขปัญหาการพึ่งพิงแบบผกผันได้ง่าย ๆ โดยการส่งผ่านพฤติกรรมเป็นฟังก์ชันแทนที่จะเป็นวัตถุที่ใช้อินเทอร์เฟซใน OO-sense

ในภาษาดังกล่าวลายเซ็นของฟังก์ชั่นสามารถกลายเป็นอินเตอร์เฟซและฟังก์ชั่นจะถูกส่งผ่านแทนวัตถุดั้งเดิมเพื่อให้พฤติกรรมที่ต้องการ หลุมในรูปแบบกลางเป็นตัวอย่างที่ดีสำหรับเรื่องนี้

มันช่วยให้คุณบรรลุผลลัพธ์เดียวกันโดยมีโค้ดน้อยลงและมีความหมายมากขึ้นเนื่องจากคุณไม่จำเป็นต้องใช้คลาสทั้งหมดที่สอดคล้องกับอินเทอร์เฟซ (OOP) เพื่อให้มีพฤติกรรมที่ต้องการสำหรับผู้โทร แต่คุณสามารถส่งคำจำกัดความของฟังก์ชั่นง่ายๆ กล่าวโดยย่อ: รหัสมักจะง่ายต่อการบำรุงรักษาแสดงออกและยืดหยุ่นมากขึ้นเมื่อใช้ฟังก์ชันคำสั่งซื้อที่สูงขึ้น

ตัวอย่างใน C #

วิธีการแบบดั้งเดิม:

public IEnumerable<Customer> FilterCustomers(IFilter<Customer> filter, IEnumerable<Customers> customers)
{
    foreach(var customer in customers)
    {
        if(filter.Matches(customer))
        {
            yield return customer;
        }
    }
}

//now you've got to implement all these filters
class CustomerNameFilter : IFilter<Customer> /*...*/
class CustomerBirthdayFilter : IFilter<Customer> /*...*/

//the invocation looks like this
var filteredDataByName = FilterCustomers(new CustomerNameFilter("SomeName"), customers);
var filteredDataBybirthDay = FilterCustomers(new CustomerBirthdayFilter(SomeDate), customers);

ด้วยฟังก์ชั่นการสั่งซื้อที่สูงขึ้น:

public IEnumerable<Customer> FilterCustomers(Func<Customer, bool> filter, IEnumerable<Customers> customers)
{
    foreach(var customer in customers)
    {
        if(filter(customer))
        {
            yield return customer;
        }
    }
}

ตอนนี้การดำเนินงานและการขอกลายเป็นเรื่องยุ่งยากน้อยลง เราไม่จำเป็นต้องจัดหาการใช้งาน IFilter อีกต่อไป เราไม่จำเป็นต้องใช้คลาสสำหรับตัวกรองอีกต่อไป

var filteredDataByName = FilterCustomers(x => x.Name.Equals("CustomerName"), customers);
var filteredDataByBirthday = FilterCustomers(x => x.Birthday == SomeDateTime, customers);

แน่นอนสิ่งนี้สามารถทำได้โดย LinQ ใน C # ฉันเพิ่งใช้ตัวอย่างนี้เพื่อแสดงให้เห็นว่ามันง่ายและยืดหยุ่นมากขึ้นในการใช้ฟังก์ชันลำดับที่สูงขึ้นแทนที่จะเป็นวัตถุที่ใช้อินเทอร์เฟซ


3
ตัวอย่างที่ดี อย่างไรก็ตามเช่นเดียวกับ Gulshan ฉันพยายามค้นหาเพิ่มเติมเกี่ยวกับการเขียนโปรแกรมเชิงฟังก์ชันและฉันก็สงสัยว่า "functional DI" แบบนี้ไม่เสียสละความรุนแรงและความสำคัญเมื่อเทียบกับ "object oriented DI" ลายเซ็นคำสั่งซื้อที่สูงกว่าระบุว่าฟังก์ชั่นที่ส่งผ่านจะต้องรับลูกค้าเป็นพารามิเตอร์และส่งคืนบูลในขณะที่เวอร์ชัน OO บังคับใช้ความจริงที่ว่าวัตถุที่ส่งผ่านนั้นเป็นตัวกรอง (ใช้ IFilter <Customer>) นอกจากนี้ยังทำให้ความคิดของตัวกรองชัดเจนซึ่งอาจเป็นสิ่งที่ดีหากเป็นแนวคิดหลักของโดเมน (ดู DDD) คุณคิดอย่างไร ?
guillaume31

2
@ ian31: นี่เป็นหัวข้อที่น่าสนใจแน่นอน! สิ่งใดก็ตามที่ถูกส่งผ่านไปยัง FilterCustomer จะทำงานเป็นตัวกรองบางประเภทโดยปริยาย เมื่อแนวคิดตัวกรองเป็นส่วนสำคัญของโดเมนและคุณต้องการกฎตัวกรองที่ซับซ้อนซึ่งใช้งานหลายครั้งในระบบ ถ้าไม่ใช่หรือเพียงแค่ระดับต่ำมากฉันจะตั้งเป้าไปที่ความเรียบง่ายทางเทคนิคและการปฏิบัตินิยม
เหยี่ยว

5
@ ian31: ฉันไม่เห็นด้วยอย่างสมบูรณ์ การนำไปใช้IFilter<Customer>นั้นไม่มีผลบังคับใช้เลย ฟังก์ชั่นการสั่งซื้อที่สูงขึ้นนั้นมีความยืดหยุ่นอย่างมากมายซึ่งเป็นผลประโยชน์ที่ยิ่งใหญ่และความสามารถในการเขียนแบบอินไลน์นั้นเป็นประโยชน์อย่างมากอีกประการหนึ่ง แลมบ์ดายังสามารถจับตัวแปรท้องถิ่นได้ง่ายขึ้น
DeadMG

3
@ ian31: ฟังก์ชั่นนี้สามารถตรวจสอบได้ในเวลารวบรวม คุณสามารถเขียนฟังก์ชั่นตั้งชื่อแล้วส่งผ่านมันเป็นอาร์กิวเมนต์ตราบใดที่มันเติมเต็มสัญญาที่ชัดเจน (รับลูกค้าคืนบูล) คุณไม่จำเป็นต้องผ่านการแสดงออกแลมบ์ดา ดังนั้นคุณสามารถครอบคลุมการขาดการแสดงออกที่ระดับหนึ่ง อย่างไรก็ตามสัญญาและความตั้งใจไม่ได้แสดงไว้อย่างชัดเจน นั่นเป็นข้อเสียที่สำคัญบางครั้ง สรุปแล้วมันเป็นเรื่องของการแสดงออกภาษาและการห่อหุ้ม ฉันคิดว่าคุณต้องตัดสินแต่ละกรณีด้วยตัวเอง
เหยี่ยว

2
ถ้าคุณรู้สึกอย่างมากเกี่ยวกับการทำความเข้าใจความหมายความหมายของฟังก์ชั่นการฉีดคุณสามารถใน C ลายเซ็นฟังก์ชั่นการใช้ # public delegate bool CustomerFilter(Customer customer)ชื่อผู้ได้รับมอบหมาย: ในภาษาทำงานบริสุทธิ์เช่น Haskell, aliasing ประเภทคือจิ๊บจ๊อย:type customerFilter = Customer -> Bool
sara

8

หากคุณต้องการเปลี่ยนพฤติกรรมของฟังก์ชั่น

doThis(Foo)

คุณสามารถผ่านฟังก์ชั่นอื่น

doThisWith(Foo, anotherFunction)

ซึ่งใช้พฤติกรรมที่คุณต้องการให้แตกต่าง

"doThisWith" เป็นฟังก์ชันที่มีลำดับสูงกว่าเนื่องจากใช้ฟังก์ชันอื่นเป็นอาร์กิวเมนต์

ตัวอย่างเช่นคุณสามารถมี

storeValues(Foo, writeToDatabase)
storeValues(Foo, imitateDatabase)

5

คำตอบสั้น ๆ :

Classical Dependency Injection / Inversion of Control ใช้คลาสอินเทอร์เฟซเป็นตัวแทนสำหรับการทำงานที่ขึ้นกับ อินเทอร์เฟซนี้ถูกใช้งานโดยคลาส

แทนที่จะใช้ Interface / ClassImplementation การพึ่งพาจำนวนมากสามารถนำไปใช้งานได้ง่ายขึ้นด้วยฟังก์ชั่นการมอบหมาย

คุณพบว่าตัวอย่างสำหรับทั้งใน c # ที่IOC-โรงงานข้อดีและข้ามสำหรับอินเตอร์เฟซที่เมื่อเทียบกับผู้ได้รับมอบหมาย


0

เปรียบเทียบสิ่งนี้:

String[] names = {"Fred", "Susan"};
List<String> namesBeginningWithS = new LinkedList<String>();
for (String name : names) {
    if (name.startsWith("S")) {
        namesBeginningWithS.add(name);
    }
}

ด้วย:

String[] names = {"Fred", "Susan"};
List<String> namesBeginningWithS = names.stream().filter(n <- n.startsWith("S")).collect();

รุ่นที่สองเป็นวิธีลด Java ของรหัส boilerplate (วนลูป ฯลฯ ) โดยให้ฟังก์ชั่นการสั่งซื้อที่สูงขึ้นเช่นfilterที่ช่วยให้คุณผ่านขั้นต่ำเปลือย (เช่นการพึ่งพาที่จะฉีด - การแสดงออกแลมบ์ดา)


0

Piggy-backup จากตัวอย่าง LennyProgrammers ...

หนึ่งในสิ่งที่ตัวอย่างอื่น ๆ ที่ไม่ได้รับคือคุณสามารถใช้ฟังก์ชันลำดับที่สูงขึ้นพร้อมกับบางส่วนของแอปพลิเคชันฟังก์ชัน (PFA) เพื่อผูก (หรือ "ฉีด") การพึ่งพาในฟังก์ชัน (ผ่านรายการอาร์กิวเมนต์) เพื่อสร้างฟังก์ชันใหม่

หากแทน:

doThisWith(Foo, anotherFunction)

เรา (เป็นแบบธรรมดาในวิธีการทำ PFA โดยทั่วไป) มีฟังก์ชั่นผู้ปฏิบัติงานระดับต่ำในฐานะ (ลำดับการสลับ ARG):

doThisWith( anotherFunction, Foo )

จากนั้นเราสามารถใช้ doThis บางส่วนได้เช่น:

doThis = doThisWith( anotherFunction )  // note that "Foo" is still missing, argument list is partial

ซึ่งทำให้เราสามารถใช้ฟังก์ชั่นใหม่ได้ในภายหลัง:

doThis(Foo)

หรือแม้กระทั่ง:

doThat = doThisWith( yetAnotherDependencyFunction )
...
doThat( Bar )

ดูเพิ่มเติมที่: https://ramdajs.com/docs/#partial

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

การขยายแนวคิดนี้ยังสามารถเพิ่มรายการอาร์กิวเมนต์ที่ยาวขึ้นไปยังฟังก์ชันพิเศษที่เพิ่มขึ้นด้วยรายการอาร์กิวเมนต์ที่สั้นลงและสั้นลงและแน่นอนว่าฟังก์ชันใด ๆ เหล่านี้สามารถส่งผ่านไปยังฟังก์ชันอื่น ๆ เป็นการอ้างอิงที่จะนำไปใช้บางส่วน

OOP เป็นสิ่งที่ดีถ้าคุณต้องการกลุ่มของสิ่งที่มีการดำเนินการที่เกี่ยวข้องอย่างใกล้ชิดหลายอย่าง แต่มันกลายเป็นเรื่องที่ต้องทำเพื่อให้ได้เรียนเป็นกลุ่ม ๆ ด้วยวิธี "do it" แบบสาธารณะ, a la "The Kingdom of Nouns"

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