Thay thế điều kiện bằng đa hình một cách thích hợp?


10

Hãy xem xét hai lớp DogCatcả hai đều tuân thủ Animalgiao thức (về ngôn ngữ lập trình Swift. Đó sẽ là giao diện trong Java / C #).

Chúng tôi có một màn hình hiển thị một danh sách hỗn hợp chó và mèo. Có Interactorlớp xử lý logic đằng sau hậu trường.

Bây giờ chúng tôi muốn đưa ra một cảnh báo xác nhận cho người dùng khi anh ta muốn xóa một con mèo. Tuy nhiên, chó cần phải được xóa ngay lập tức mà không có bất kỳ cảnh báo. Phương thức với các điều kiện sẽ trông như thế này:

func tryToDeleteModel(model: Animal) {
    if let model = model as? Cat {
        tellSceneToShowConfirmationAlert()
    } else if let model = model as? Dog {
        deleteModel(model: model)
    }
}

Làm thế nào mã này có thể được tái cấu trúc? Nó rõ ràng có mùi

Câu trả lời:


9

Bạn đang để loại giao thức tự xác định hành vi. Bạn muốn đối xử với tất cả các giao thức giống nhau trong suốt chương trình của mình ngoại trừ trong chính lớp triển khai. Làm theo cách này là tôn trọng Nguyên tắc thay thế của Liskov, nói rằng bạn sẽ có thể vượt qua Cathoặc Dog(hoặc bất kỳ giao thức nào khác mà cuối cùng bạn có thể có Animalkhả năng) và để nó hoạt động một cách vô tư.

Vì vậy, có lẽ bạn sẽ thêm một chức isCriticalnăng Animalđể được thực hiện bởi cả hai DogCat. Bất cứ điều gì thực hiện Dogsẽ trả về sai và bất cứ điều gì thực hiện Catsẽ trở lại đúng.

Tại thời điểm đó, bạn chỉ cần thực hiện (Tôi xin lỗi nếu cú ​​pháp không chính xác. Không phải là người dùng Swift):

func tryToDeleteModel(model: Animal) {
    if model.isCritical() {
        tellSceneToShowConfirmationAlert()
    } else {
        deleteModel(model: model)
    }
}

Chỉ có một vấn đề nhỏ với điều đó, và đó là DogCatlà giao thức, có nghĩa là họ trong bản thân mình không xác định isCriticallợi nhuận, để lại này để mỗi lớp thực hiện để quyết định cho bản thân. Nếu bạn có nhiều triển khai, có thể đáng để bạn tạo ra một lớp có thể mở rộng Cathoặc Dogđã triển khai chính xác isCriticalvà xóa hiệu quả tất cả các lớp thực hiện khỏi nhu cầu ghi đè isCritical.

Nếu điều này không trả lời câu hỏi của bạn, xin vui lòng viết bình luận và tôi sẽ mở rộng câu trả lời của mình cho phù hợp!


Có một chút không rõ ràng trong tuyên bố của câu hỏi, nhưng DogCatđược mô tả là các lớp, trong khi đó Animallà một giao thức được thực hiện bởi mỗi lớp đó. Vì vậy, có một chút không phù hợp giữa câu hỏi và câu trả lời của bạn.
Caleb

Vì vậy, bạn đề nghị để cho mô hình quyết định có xuất hiện một cửa sổ bật lên xác nhận hay không? Nhưng điều gì sẽ xảy ra nếu có một logic nặng nề liên quan, như chỉ hiển thị cửa sổ bật lên nếu có 10 con mèo được hiển thị? Logic Interactorbây giờ phụ thuộc vào trạng thái
Andrey Gordeev

Vâng, xin lỗi về câu hỏi không rõ ràng, tôi đã thực hiện một vài chỉnh sửa. Nên rõ ràng hơn bây giờ
Andrey Gordeev

1
Loại hành vi này không nên được liên kết với mô hình. Nó phụ thuộc vào bối cảnh và không phải bản thân thực thể. Tôi nghĩ rằng Cat và Dog có nhiều khả năng là POJO. Hành vi nên được xử lý ở những nơi khác và có thể thay đổi theo bối cảnh. Ủy thác các hành vi hoặc phương pháp mà các hành vi sẽ dựa vào Mèo hoặc Chó sẽ dẫn đến quá nhiều trách nhiệm trong các lớp đó.
Grégory Elhaimer

@ GrégoryElhaimer Xin lưu ý rằng nó không xác định hành vi. Nó chỉ đơn thuần là cho biết đó có phải là một lớp quan trọng hay không. Các hành vi trong suốt chương trình cần biết nếu đó là một lớp quan trọng thì có thể đánh giá và hành động tương ứng. Nếu đây thực sự là một thuộc tính phân biệt các trường hợp trong cả hai CatDogđược xử lý, thì nó có thể và nên là một thuộc tính chung Animal. Làm bất cứ điều gì khác là yêu cầu bảo trì đau đầu sau này.
Neil

4

Nói so với hỏi

Cách tiếp cận có điều kiện mà bạn đang thể hiện, chúng tôi sẽ gọi " hỏi ". Đây là nơi khách hàng tiêu dùng hỏi "bạn là loại gì?" và tùy chỉnh hành vi và tương tác của họ với các đối tượng phù hợp.

Điều này trái ngược với sự thay thế mà chúng ta gọi là " kể ". Sử dụng Tell , bạn đẩy nhiều công việc hơn vào các triển khai đa hình, để mã khách hàng tiêu thụ đơn giản hơn, không có điều kiện và phổ biến bất kể việc triển khai có thể xảy ra.

Vì bạn muốn sử dụng cảnh báo xác nhận, bạn có thể làm cho khả năng rõ ràng của giao diện. Vì vậy, bạn có thể có một phương thức boolean tùy chọn kiểm tra với người dùng và trả về boolean xác nhận. Trong các lớp không muốn xác nhận, họ chỉ cần ghi đè lên return true;. Các triển khai khác có thể tự động xác định nếu họ muốn sử dụng xác nhận.

Khách hàng tiêu thụ sẽ luôn sử dụng phương thức xác nhận bất kể lớp con cụ thể mà nó đang làm việc với nó, điều này làm cho sự tương tác nói thay vì hỏi .

(Một cách tiếp cận khác là đẩy xác nhận vào xóa, nhưng điều đó sẽ gây ngạc nhiên cho những khách hàng tiêu dùng mong muốn thao tác xóa thành công.)


Vì vậy, bạn đề nghị để cho mô hình quyết định có xuất hiện một cửa sổ bật lên xác nhận hay không? Nhưng điều gì sẽ xảy ra nếu có một logic nặng nề liên quan, như chỉ hiển thị cửa sổ bật lên nếu có 10 con mèo được hiển thị? Logic Interactorbây giờ phụ thuộc vào trạng thái
Andrey Gordeev

2
Ok, vâng, đó là một câu hỏi khác nhau, đòi hỏi một câu trả lời khác nhau.
Erik Eidt

2

Xác định xem có cần xác nhận hay không là trách nhiệm của Catlớp, vì vậy hãy cho phép nó thực hiện hành động đó. Tôi không biết Kotlin, vì vậy tôi sẽ diễn đạt mọi thứ bằng C #. Hy vọng rằng các ý tưởng sau đó cũng có thể được chuyển sang Kotlin.

interface Animal
{
    bool IsOkToDelete();
}

class Cat : Animal
{
    private readonly Func<bool> _confirmation;

    public Cat (Func<bool> confirmation) => _confirmation = confirmation;

    public bool IsOkToDelete() => _confirmation();
}

class Dog : Animal
{
    public bool IsOkToDelete() => true;
}

Sau đó, khi tạo một Catcá thể, bạn cung cấp nó TellSceneToShowConfirmationAlert, nó sẽ cần trả về truenếu OK để xóa:

var model = new Cat(TellSceneToShowConfirmationAlert);

Và sau đó chức năng của bạn trở thành:

void TryToDeleteModel(Animal model) 
{
    if (model.IsOKToDelete())
    {
        DeleteModel(model)
    }
}

1
Điều này không di chuyển logic xóa vào mô hình? Sẽ không tốt hơn nhiều nếu sử dụng một đối tượng khác để xử lý việc này? Có thể là cấu trúc dữ liệu như Từ điển <Cat> bên trong ApplicationService; kiểm tra xem Cat có tồn tại không và nếu có thì sẽ tắt cảnh báo xác nhận?
keelerjr12

@ keelerjr12, nó chuyển trách nhiệm xác định xem có cần xác nhận để xóa vào Catlớp không. Tôi tranh luận rằng đó là nơi nó thuộc về. Nó không được quyết định làm thế nào để đạt được xác nhận đó (được tiêm) và nó không tự xóa. Vì vậy, không, nó không di chuyển logic xóa vào mô hình.
David Arno

2
Tôi cảm thấy cách tiếp cận này sẽ dẫn đến hàng tấn mã liên quan đến giao diện người dùng gắn liền với chính lớp đó. Nếu lớp dự định được sử dụng trên nhiều lớp UI, vấn đề sẽ tăng lên. Tuy nhiên, nếu đây là lớp loại ViewModel, chứ không phải là một thực thể kinh doanh, thì nó có vẻ phù hợp.
Graham

@Graham, vâng, đó chắc chắn là một rủi ro với phương pháp này: nó phụ thuộc vào việc dễ dàng đưa TellSceneToShowConfirmationAlertvào một ví dụ Cat. Trong những tình huống không phải là một điều dễ dàng (chẳng hạn như trong một hệ thống nhiều lớp, nơi chức năng này nằm ở mức sâu), thì cách tiếp cận này sẽ không phải là một cách tốt.
David Arno

1
Chính xác những gì tôi đã nhận được tại. Một thực thể kinh doanh so với một lớp ViewModel. Trong lĩnh vực kinh doanh, một chú mèo không nên biết về mã liên quan đến giao diện người dùng. Con mèo của gia đình tôi không cảnh báo cho bất cứ ai. Cảm ơn!
keelerjr12

1

Tôi sẽ tư vấn để đi cho một mô hình khách truy cập. Tôi đã thực hiện một triển khai nhỏ trong Java. Tôi không quen thuộc với Swift, nhưng bạn có thể điều chỉnh nó dễ dàng.

Người khách

public interface AnimalVisitor<R>{
    R visitCat();
    R visitDog();
}

Mô hình của bạn

abstract class Animal { // can also be an interface like VisitableAnimal
    abstract <R> R accept(AnimalVisitor<R> visitor);
}

class Cat extends Animal {
    public <R> R accept(AnimalVisitor<R> visitor) {
         return visitor.visitCat();
     }
}

class Dog extends Animal {
    public <R> R accept(AnimalVisitor<R> visitor) {
         return visitor.visitDog();
     }
}

Gọi cho khách

public void tryToDelete(Animal animal) {
    animal.accept( new AnimalVisitor<Void>() {
        public Void visitCat() {
            tellSceneToShowConfirmation();
            return null;
        }

        public Void visitDog() {
            deleteModel(animal);
            return null;
        }
    });
}

Bạn có thể có nhiều triển khai AnimalVisitor như bạn muốn.

Thí dụ:

public void isColorValid(Color color) {
    animal.accept( new AnimalVisitor<Boolean>() {
        public Boolean visitCat() {
            return Color.BLUE.equals(color);
        }

        public Boolean visitDog() {
            return true;
        }
    });
}
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.