Fluent Interface và Nguyên lý LoD huyền thoại

Fluent Interface và Nguyên lý LoD huyền thoại

December 3, 2020 1 By Ntech Developers

The Law of Demeter Principle – LoD một nguyên lý khá hay nhưng khó hiểu trong lập trình, việc sử dụng nó hay không và khi nào thì thực sự có rất nhiều trường phái khác nhau. Nhưng với mình nếu nắm được điểm cốt lõi của nó thì có lẽ sẽ giúp cho việc lập trình dễ dàng hơn rất nhiều.

Cùng mình tìm hiểu Fluent Interface và nguyên lý LoD nhé!

Không biết có bạn nào từng chơi lego chưa, nó là những mảnh phép lại với nhau từ những mảnh nhỏ, với mình đó là một hình ảnh thực tế của Fluent Interface. Hay nếu bạn nào đã từng biết đến lập trình cho trẻ nhỏ với những mảnh ghép lệnh kiểu như Scratch thì bạn cũng phần nào hình dung được cách thức hoạt động của Fluent Interface

Fluent interface được đặt ra bởi Eric Evans và Martin Fowler, là sự mở rộng của kỹ thuật “Lời gọi theo chuỗi” (method chaining) nhằm làm cho mã lập trình dễ đọc hơn. Method chaining là một kỹ thuật về cú pháp chung cho cách gọi nhiều lần tới các hàm khác trong lập trình hướng đối tượng.
Fluent-Interface là cách design API để làm những việc phức tạp, nhiều bước dưới dạng văn xuôi, gần gũi với con người.
Bất kể ngôn ngữ gì, bạn chắc hẳng đã gặp qua kiểu dấu chấm nối huyền thoại này rồi, nhưng mình tin là bạn chỉ dùng nhưng mà ít khi để ý tới nó được xây dựng như thế nào đâu nhỉ.

store.getOrder()
     .getCustomer()
     .getBillingAddress()
     .getCity();


$person->setName('Ntech')
       ->setAge(27)
       ->showInfo();


$('#element').first()
             .slideDown()
             .remove();

Nếu như bạn có một chuỗi các logic thì đơn giản là bạn viết ra những hàm kiểu như vậy sau đó return về chính đối tượng đầu vào bạn tiếp nhận kia thì thực sự nhìn rất mê, cảm giác như lập trình có khó gì đâu, chỉ là ghép những mảnh lego lại với nhau thôi mà 😄

Ví dụ:

public class Person
{
    private string _name;
    private int _age;

    public Person setName(string name)
    {
        this._name = name;
        return this;
    }

    public Person setAge(int age)
    {
        this._age = age;
        return this;
    }

    public Person showInfo()
    {
        console.write($"Name: {this._name}, age: {this._age});
    }
}
var person = new Person();
person.setName('Ntech')
      .setAge(27)
      .showInfo();

Bạn thấy đó cách viết ra nó cũng thật dễ dàng đúng không? Cách viết này rất phổ biết đối với code base của những framework hay thư viện lớn như Microsoft, Azure hay Entity Framework… rảnh thì bạn có thể vào xem trong reference source của microsoft thử@@

await Azure
    .Configure()
    .WithLogLevel(Level.Basic)
    .Authenticate(credentials)
    .WithSubscription("subscriptionId")
    .SqlServers
    .Define("sqlServerName")
    .WithRegion(Region.EuropeWest)
    .WithNewResourceGroup()
    .WithAdministratorLogin("sqlAdmin")
    .WithAdministratorPassword("pass")
    .WithNewDatabase("databaseOne")
    .WithNewDatabase("databaseTwo")
    .WithNewFirewallRule("0.0.0.0")
    .WithNewFirewallRule("1.1.1.1")
    .WithNewFirewallRule("2.2.2.2")
    .CreateAsync();
modelBuilder
    .Entity<Foo>()
    .HasMany(foo => foo.Bars)
    .WithOne(bar => bar.Foo)
    .HasForeignKey(bar => bar.FooId)
    .OnDelete(DeleteBehavior.Cascade);
    
modelBuilder
    .Entity<Foo>()
    .Property(foo => foo.Value)
    .HasDefaultValue("0123456789")
    .HasMaxLength(10)
    .IsFixedLength()
    .IsRequired();

Có một cách viết cùng cha khác ông nội với Fluent Interface đó chính là Extension Method. Điểm khác biệt ở đây là nó cho phép mở rộng thêm mới phương thức cho một kiểu có sẵn. Ví dụ kiểu dữ liệu string mình muốn viết thêm phương thức chuẩn hoá chuỗi (viết hoa ký tự đâu, mỗi từ chỉ cách nhau một dấu cách) thì mình chỉ cần viết thêm this ở tham số đầu vào cho hàm mà thôi (cú pháp và sự hỗ trợ của mỗi ngôn ngữ là khác nhau nhé! Ở đây mình đề cập đến C#).

Bạn có thể nghiên cứu thêm Extension Method với nhiều ngôn ngữ khác nhau ở đây 
https://www.extensionmethod.net

Được rồi kỹ thuật viết này hay thật đó nhưng mà nó lại mang một sự tranh cãi lớn đó là nguyên lý LoD được định nghĩa như sau.

LoD là viết tắt của Law of Demeter Principle còn gọi khác là nguyên tắc Demeter hay nguyên tắc dấu chấm. Nó là một nguyên tắc thiết kế để phát triển phần mềm, đặc biệt là các chương trình hướng đối tượng.

LoD là một triết lý nền tảng của việc lập trình được sinh ra từ một aspect-oriented programming (AOP) project cùng tên, là một trường hợp cụ thể của khớp nối lỏng lẻo (loose coupling).
Với nguyên tắc này thì nó lại lên án kiểu như là không nên gọi quá nhiều dấu chấm (lời gọi hàm), là 1 code smell và sẽ dẫn đến việc code rất dễ vỡ khi có thay đổi.
Về cơ bản thì nguyên tắc nhắm đến là tối giản sự hiểu biết của 1 object về cấu trúc, thuộc tính của các object khác ngoài nó (bao gồm các thành phần con)

Với nguyên lý này thì đoạn code bên trên của mình sẽ gặp lỗi sau. Nếu mình không gọi hàm setName() và setAge() mà trực tiếp gọi hàm showInfo() có thể sẽ bị lỗi NullPointerException huyền thoại cho những giá trị null. Tuy nhiên, việc đóng gói tái sử dụng sẽ dễ dàng hơn rất nhiều, tránh sự phụ thuộc, ví dụ mình chỉ muốn đóng gói sử dụng mỗi setName() thay vì cả setAge(), thì mình chỉ gắn mỗi dấu chấm đầu thôi.
Dù sao thì vấn đề handle validate trước khi return về mà xử lý chặt chẽ thì mình tin đây là một cách thức implement rất hay ho.

Nếu bạn là dev, không biết bạn đã từng gõ code trên notepad thường chưa nhỉ, khi mà bạn quá quen với việc nhắc từ khoá cũng như chấm sổ là nhắc hàm thì có lẽ bạn sẽ rất khó chịu ý nhỉ. Mình thấy đây là lợi ích lớn nhất của Fluent Interface và Extension Method

Còn bạn bạn nghĩ sao về 2 cách viết code này và nguyên tắc LoD?