Làm thế nào là nghịch đảo phụ thuộc liên quan đến các chức năng bậc cao?


41

Hôm nay tôi mới thấy bài viết này mô tả sự liên quan của nguyên tắc RẮN trong phát triển F #

F # và nguyên tắc thiết kế - RẮN

Và trong khi giải quyết vấn đề cuối cùng - "Nguyên tắc đảo ngược phụ thuộc", tác giả cho biết:

Từ quan điểm chức năng, các thùng chứa và khái niệm tiêm này có thể được giải quyết bằng một hàm bậc cao đơn giản hơn, hoặc kiểu mẫu giữa lỗ được xây dựng ngay trong ngôn ngữ.

Nhưng anh không giải thích thêm. Vì vậy, câu hỏi của tôi là, nghịch đảo phụ thuộc liên quan đến các hàm bậc cao hơn như thế nào?

Câu trả lời:


38

Sự phụ thuộc đảo ngược trong OOP có nghĩa là bạn mã hóa một giao diện mà sau đó được cung cấp bởi một triển khai trong một đối tượng.

Các ngôn ngữ hỗ trợ các chức năng ngôn ngữ cao hơn thường có thể giải quyết các vấn đề đảo ngược phụ thuộc đơn giản bằng cách chuyển hành vi dưới dạng hàm thay vì một đối tượng thực hiện giao diện theo nghĩa OO.

Trong các ngôn ngữ như vậy, chữ ký của hàm có thể trở thành giao diện và một hàm được truyền vào thay vì một đối tượng truyền thống để cung cấp hành vi mong muốn. Lỗ hổng trong mẫu giữa là một ví dụ tốt cho điều này.

Nó cho phép bạn đạt được kết quả tương tự với ít mã hơn và biểu cảm hơn, vì bạn không cần phải thực hiện cả một lớp phù hợp với giao diện (OOP) để cung cấp hành vi mong muốn cho người gọi. Thay vào đó, bạn chỉ có thể vượt qua một định nghĩa hàm đơn giản. Tóm lại: Mã thường dễ bảo trì hơn, biểu cảm hơn và linh hoạt hơn khi người ta sử dụng các hàm bậc cao hơn.

Một ví dụ trong C #

Cách tiếp cận truyền thống:

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

Với các chức năng bậc cao hơn:

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

Bây giờ việc thực hiện và gọi trở nên ít cồng kềnh hơn. Chúng tôi không cần phải cung cấp triển khai IFilter nữa. Chúng ta không cần phải thực hiện các lớp cho các bộ lọc nữa.

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

Tất nhiên, điều này đã có thể được LinQ thực hiện trong C #. Tôi chỉ sử dụng ví dụ này để minh họa rằng việc sử dụng các hàm bậc cao hơn thay vì các đối tượng thực hiện giao diện sẽ dễ dàng và linh hoạt hơn.


3
Ví dụ tốt đẹp. Tuy nhiên, giống như Gulshan tôi đang cố gắng tìm hiểu thêm về lập trình chức năng và tôi đã tự hỏi liệu loại "DI chức năng" này không hy sinh một số nghiêm ngặt và ý nghĩa so với "DI hướng đối tượng". Chữ ký bậc cao hơn chỉ nói rằng hàm được truyền phải lấy một Khách hàng làm tham số và trả về một bool trong khi phiên bản OO thực thi thực tế rằng đối tượng được truyền một bộ lọc (thực hiện IFilter <Khách hàng>). Nó cũng làm cho khái niệm bộ lọc rõ ràng, có thể là một điều tốt nếu đó là một khái niệm cốt lõi của Miền (xem DDD). Bạn nghĩ sao ?
guillaume31

2
@ ian31: Đây thực sự là một chủ đề thú vị! Bất cứ điều gì được chuyển đến FilterCustomer sẽ hoạt động như một loại bộ lọc ngầm. Khi khái niệm bộ lọc là một phần thiết yếu của miền và bạn cần các quy tắc bộ lọc phức tạp được sử dụng nhiều lần trên hệ thống, chắc chắn sẽ tốt hơn để đóng gói chúng. Nếu không hoặc chỉ ở một mức độ rất thấp, thì tôi sẽ nhắm đến sự đơn giản về kỹ thuật và tính thực dụng.
Falcon

5
@ ian31: Tôi hoàn toàn không đồng ý. Thực hiện IFilter<Customer>là không thực thi chút nào. Hàm bậc cao linh hoạt hơn rất nhiều, đó là một lợi ích lớn và có thể viết chúng theo dòng là một lợi ích to lớn khác. Lambdas cũng dễ dàng hơn nhiều để nắm bắt các biến cục bộ.
DeadMG

3
@ ian31: Hàm cũng có thể được xác minh tại thời gian biên dịch. Bạn cũng có thể viết một hàm, đặt tên cho nó, và sau đó chuyển nó thành một đối số miễn là nó điền đầy đủ hợp đồng rõ ràng (lấy khách hàng, trả về bool). Bạn không nhất thiết phải vượt qua một biểu thức lambda. Vì vậy, bạn có thể bao gồm sự thiếu biểu cảm ở một mức độ nhất định. Tuy nhiên, hợp đồng và ý định của nó không được thể hiện rõ ràng. Đó là một bất lợi lớn đôi khi. Tất cả trong tất cả đó là vấn đề biểu cảm, ngôn ngữ và đóng gói. Tôi nghĩ bạn phải tự đánh giá từng trường hợp.
Falcon

2
nếu bạn cảm thấy mạnh mẽ về việc làm rõ ý nghĩa ngữ nghĩa của hàm được chèn, bạn có thể ký tên hàm C # bằng cách sử dụng các đại biểu : public delegate bool CustomerFilter(Customer customer). trong các ngôn ngữ chức năng thuần túy như haskell, các kiểu răng cưa là tầm thường:type customerFilter = Customer -> Bool
sara

8

Nếu bạn muốn thay đổi hành vi của một chức năng

doThis(Foo)

bạn có thể vượt qua một chức năng khác

doThisWith(Foo, anotherFunction)

trong đó thực hiện các hành vi bạn muốn khác nhau.

"do ThisWith" là một hàm bậc cao hơn vì nó lấy một hàm khác làm đối số.

Ví dụ bạn có thể có

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

5

Câu trả lời ngắn:

Tiêm / phụ thuộc điều khiển cổ điển sử dụng giao diện lớp làm trình giữ chỗ cho chức năng phụ thuộc. Giao diện này được thực hiện bởi một lớp.

Thay vì Giao diện / ClassImcellenceation, nhiều phụ thuộc có thể được thực hiện dễ dàng hơn với chức năng ủy nhiệm.

Bạn tìm thấy một ví dụ cho cả hai trong c # tại ioc-Factory-pros-and-contras-for-interface-vs.-đại biểu .


0

So sánh điều này:

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

với:

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

Phiên bản thứ hai là cách giảm mã soạn sẵn của Java 8 (lặp, v.v.) bằng cách cung cấp các hàm bậc cao hơn như filtercho phép bạn vượt qua mức tối thiểu (nghĩa là phụ thuộc được chèn - biểu thức lambda).


0

Piggy ủng hộ ví dụ LennyProgrammer ...

Một trong những điều mà các ví dụ khác đã bỏ lỡ là bạn có thể sử dụng các hàm bậc cao hơn cùng với ứng dụng hàm một phần (PFA) để liên kết (hoặc "tiêm") vào một hàm (thông qua danh sách đối số của nó) để tạo hàm mới.

Nếu thay vì:

doThisWith(Foo, anotherFunction)

chúng tôi (theo thông lệ về cách PFA thường được thực hiện) có chức năng worker mức thấp như (hoán đổi thứ tự arg):

doThisWith( anotherFunction, Foo )

Sau đó chúng ta có thể áp dụng một phần do ThisWith như vậy:

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

Cho phép chúng ta sau này sử dụng chức năng mới như vậy:

doThis(Foo)

Hoặc thậm chí:

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

Xem thêm: https://ramdajs.com/docs/#partial

... Và, vâng, các bộ cộng / số nhân là những ví dụ không thể tưởng tượng được. Một ví dụ tốt hơn sẽ là một chức năng nhận tin nhắn và ghi nhật ký hoặc gửi email cho họ tùy thuộc vào chức năng "người tiêu dùng" được truyền vào như là một phụ thuộc là gì.

Mở rộng ý tưởng này, các danh sách đối số dài hơn vẫn có thể được thu hẹp dần dần thành các hàm ngày càng chuyên biệt hơn với các danh sách đối số ngắn hơn và ngắn hơn, và tất nhiên bất kỳ chức năng nào trong số này có thể được chuyển sang các chức năng khác khi phụ thuộc được áp dụng một phần.

OOP là tốt nếu bạn cần một bó nhiều thứ với nhiều hoạt động liên quan chặt chẽ, nhưng nó biến thành công việc tạo ra một nhóm các lớp với một phương thức "làm điều đó" công khai, gọi là "Vương quốc danh từ".

Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.