Việc tách hầu hết các lớp thành lớp dữ liệu chỉ lớp và phương thức chỉ các lớp (nếu có thể) là tốt hay chống mẫu?


10

Ví dụ, một lớp thường có các thành viên và phương thức của lớp, ví dụ:

public class Cat{
    private String name;
    private int weight;
    private Image image;

    public void printInfo(){
        System.out.println("Name:"+this.name+",weight:"+this.weight);
    }

    public void draw(){
        //some draw code which uses this.image
    }
}

Nhưng sau khi đọc về nguyên tắc trách nhiệm đơn và nguyên tắc đóng mở, tôi thích tách một lớp thành DTO và lớp trình trợ giúp chỉ với các phương thức tĩnh, ví dụ:

public class CatData{
    public String name;
    public int weight;
    public Image image;
}

public class CatMethods{
    public static void printInfo(Cat cat){
        System.out.println("Name:"+cat.name+",weight:"+cat.weight);
    }

    public static void draw(Cat cat){
        //some draw code which uses cat.image
    }
}

Tôi nghĩ rằng nó phù hợp với nguyên tắc trách nhiệm duy nhất bởi vì bây giờ trách nhiệm của CatData là chỉ giữ dữ liệu, không quan tâm đến các phương thức (cũng đối với CatMethods). Và nó cũng phù hợp với nguyên tắc đóng mở vì thêm các phương thức mới không cần thay đổi lớp CatData.

Câu hỏi của tôi là, nó là một mô hình tốt hay chống?


15
Làm bất cứ điều gì mù quáng ở mọi nơi vì bạn đã học được một khái niệm mới luôn là một mô hình chống đối.
Kayaman

4
Điểm của các lớp sẽ là gì nếu luôn có dữ liệu tách biệt với các phương thức sửa đổi chúng? Nghe có vẻ như lập trình không hướng đối tượng (chắc chắn có vị trí của nó). Vì đây được gắn thẻ "hướng đối tượng", tôi giả sử bạn muốn sử dụng các công cụ mà OOP cung cấp?
dùng1118321

4
Một câu hỏi khác chứng minh rằng SRP bị hiểu lầm nghiêm trọng đến mức cần phải bị cấm. Giữ dữ liệu trong một loạt các lĩnh vực công cộng không phải là một trách nhiệm .
dùng949300

1
@ user949300, nếu lớp chịu trách nhiệm cho các trường đó, thì việc di chuyển chúng đi nơi khác trên ứng dụng tất nhiên sẽ không ảnh hưởng. Hay nói cách khác, bạn đã nhầm: họ 100% là một trách nhiệm.
David Arno

2
@DavidArno và R Schmitz: Chứa các trường không được tính là trách nhiệm cho các mục đích của SRP. Nếu có, không thể có OOP, vì mọi thứ sẽ là DTO, và sau đó các lớp riêng biệt sẽ chứa các quy trình để làm việc trên dữ liệu. Một trách nhiệm liên quan đến một bên liên quan . (Mặc dù điều này dường như thay đổi một chút so với mỗi lần lặp của hiệu trưởng)
user949300

Câu trả lời:


10

Bạn đã chỉ ra hai phương thức ("mọi thứ riêng tư và tất cả (có thể không liên quan) trong một đối tượng" so với "mọi thứ công khai và không có phương thức nào bên trong đối tượng"). IMHO người mẫu OO giỏi không ai trong số họ , điểm ngọt ngào nằm ở đâu đó ở giữa.

Một bài kiểm tra về các phương thức hoặc logic thuộc về một lớp và những gì thuộc về bên ngoài, là xem xét các phụ thuộc mà các phương thức sẽ đưa ra. Các phương thức không đưa ra các phụ thuộc bổ sung là tốt, miễn là chúng phù hợp với sự trừu tượng của đối tượng đã cho . Các phương thức yêu cầu bổ sung, phụ thuộc bên ngoài (như thư viện vẽ hoặc thư viện I / O) hiếm khi phù hợp. Ngay cả khi bạn sẽ làm cho các phụ thuộc biến mất bằng cách sử dụng phép nội xạ phụ thuộc, tôi vẫn sẽ nghĩ hai lần nếu đặt các phương thức như vậy trong lớp miền là thực sự cần thiết.

Vì vậy, bạn không nên làm cho mọi thành viên công khai, bạn cũng không cần phải thực hiện các phương thức cho mọi hoạt động trên một đối tượng bên trong lớp. Đây là một gợi ý thay thế:

public class Cat{
    private String name;
    private int weight;
    private Image image;

    public String getInfo(){
        return "Name:"+this.name+",weight:"+this.weight;
    }
    public Image getImage(){
        return image;
    }
}

Bây giờ Catđối tượng cung cấp đủ logic để cho mã xung quanh dễ dàng thực hiện printInfodraw, mà không để lộ tất cả các thuộc tính ở chế độ công khai. Vị trí phù hợp cho hai phương thức này có lẽ không phải là một lớp thần CatMethods(vì printInfodrawcó lẽ là những mối quan tâm khác nhau, vì vậy tôi nghĩ rằng rất có thể chúng không thuộc cùng một lớp).

Tôi có thể tưởng tượng một CatDrawingControllercái mà thực hiện draw(và có thể sử dụng phép nội xạ phụ thuộc để lấy một đối tượng Canvas). Tôi cũng có thể tưởng tượng một lớp khác thực hiện một số đầu ra và sử dụng bàn điều khiển getInfo(vì vậy printInfocó thể trở nên lỗi thời trong bối cảnh này). Nhưng để đưa ra quyết định hợp lý về vấn đề này, người ta cần biết bối cảnh và cách Catlớp sẽ thực sự được sử dụng.

Đó thực sự là cách tôi diễn giải các nhà phê bình Mô hình miền thiếu máu của Fowler - đối với logic thường sử dụng lại (không phụ thuộc bên ngoài), bản thân các lớp miền là một nơi tốt, vì vậy chúng nên được sử dụng cho điều đó. Nhưng điều đó không có nghĩa là thực hiện bất kỳ logic nào ở đó, hoàn toàn ngược lại.

Cũng lưu ý ví dụ trên vẫn còn chỗ để đưa ra quyết định về khả năng biến đổi (im). Nếu Catlớp sẽ không để lộ bất kỳ setters nào và Imagebản thân nó là bất biến, thiết kế này sẽ cho phép tạo ra Catbất biến (mà cách tiếp cận DTO sẽ không). Nhưng nếu bạn nghĩ rằng sự bất biến là không bắt buộc hoặc không hữu ích cho trường hợp của bạn, bạn cũng có thể đi theo hướng đó.


Kích hoạt hạnh phúc downvoters, điều này được nhàm chán. Nếu bạn có một số chỉ trích, xin vui lòng cho tôi biết. Tôi sẽ rất vui để làm rõ bất kỳ sự hiểu lầm, nếu bạn có một số.
Doc Brown

Mặc dù tôi thường đồng ý với câu trả lời của bạn ... Tôi cảm thấy getInfo()có thể gây hiểu lầm cho ai đó hỏi câu hỏi này. Nó khéo léo pha trộn các trách nhiệm thuyết trình như đã printInfo()làm, đánh bại mục đích của DrawingControllerlớp học
Adriano Repetti

@AdrianoRepetti Tôi không thấy rằng nó đang đánh bại mục đích DrawingController. Tôi đồng ý rằng getInfo()printInfo()là những cái tên khủng khiếp. Hãy xem xét toString()printTo(Stream stream)
candied_orange

@candid không phải (chỉ) về cái tên mà là về những gì nó làm. Mã đó chỉ là về một chi tiết trình bày và chúng tôi chỉ trừu tượng hóa đầu ra chứ không phải nội dung được xử lý và logic của nó.
Adriano Repetti

@AdrianoRepetti: thành thật mà nói, đây chỉ là một ví dụ giả định, để phù hợp với câu hỏi ban đầu và chứng minh quan điểm của tôi. Trong một hệ thống thực, sẽ có bối cảnh xung quanh cho phép đưa ra quyết định về các phương pháp và tên tốt hơn, tất nhiên.
Doc Brown

6

Trả lời muộn nhưng tôi không thể cưỡng lại.

Là hầu hết các lớp vào Y tốt hay chống mẫu?

Trong hầu hết các trường hợp, hầu hết các quy tắc, được áp dụng mà không cần suy nghĩ, hầu hết sẽ sai lầm khủng khiếp (bao gồm cả quy tắc này).

Hãy để tôi kể cho bạn một câu chuyện về sự ra đời của một vật thể giữa sự hỗn loạn của một số quy tắc thủ tục đúng đắn, nhanh chóng và bẩn thỉu đã xảy ra, không phải do thiết kế, mà là tuyệt vọng.

Thực tập sinh của tôi và tôi đang lập trình cặp để nhanh chóng tạo ra một số mã vứt đi để cạo trang web. Chúng tôi hoàn toàn không có lý do để mong đợi mã này sẽ tồn tại lâu, vì vậy chúng tôi chỉ cần đập ra một cái gì đó hoạt động. Chúng tôi lấy toàn bộ trang dưới dạng một chuỗi và cắt ra những thứ chúng tôi cần theo cách dễ vỡ nhất đến mức bạn có thể tưởng tượng. Đừng phán xét. Nó hoạt động.

Bây giờ trong khi làm điều này, tôi đã tạo ra một số phương thức tĩnh để thực hiện việc băm nhỏ. Thực tập sinh của tôi đã tạo ra một lớp DTO rất giống với bạn CatData.

Khi tôi lần đầu tiên nhìn vào DTO, nó đã làm tôi khó chịu. Những năm thiệt hại mà Java đã gây ra cho bộ não của tôi khiến tôi phải giật mình ở các lĩnh vực công cộng. Nhưng chúng tôi đã làm việc trong C #. C # không cần các getters và setters sớm để bảo vệ quyền của bạn để làm cho dữ liệu không thay đổi hoặc được gói gọn sau đó. Không cần thay đổi giao diện, bạn có thể thêm chúng bất cứ khi nào bạn muốn. Có lẽ chỉ để bạn có thể thiết lập một điểm dừng. Tất cả mà không nói với khách hàng của bạn một điều về nó. Yea C #. Boo Java.

Vì vậy, tôi giữ lưỡi của tôi. Tôi quan sát khi anh ta sử dụng các phương thức tĩnh của tôi để khởi tạo thứ này trước khi sử dụng nó. Chúng tôi có khoảng 14 người trong số họ. Đó là, xấu xí, nhưng chúng tôi không có lý do để quan tâm.

Sau đó, chúng tôi cần nó ở những nơi khác. Chúng tôi thấy mình muốn sao chép và dán mã. 14 dòng khởi tạo đang được ném xung quanh. Nó đã bắt đầu đau đớn. Anh ngập ngừng và hỏi tôi ý kiến.

Tôi miễn cưỡng hỏi, "bạn sẽ xem xét một đối tượng?"

Anh nhìn lại DTO của mình và bối rối bối rối. "Đó là một đối tượng".

"Tôi có nghĩa là một đối tượng thực sự"

"Huh?"

"Hãy để tôi chỉ cho bạn một cái gì đó. Bạn quyết định xem nó có hữu ích không"

Tôi đã chọn một tên mới và nhanh chóng lấy ra một cái gì đó trông giống như thế này:

public class Cat{
    CatData(string catPage) {
        this.catPage = catPage
    }
    private readonly string catPage;

    public string name() { return chop("name prefix", "name suffix"); }
    public string weight() { return chop("weight prefix", "weight suffix"); }
    public string image() { return chop("image prefix", "image suffix"); }

    private string chop(string prefix, string suffix) {
        int start = catPage.indexOf(prefix) + prefix.Length;
        int end = catPage.indexOf(suffix);
        int length = end - start;
        return catPage.Substring(start, length);
    }
}

Điều này không làm gì các phương thức tĩnh chưa làm. Nhưng bây giờ tôi đã hút 14 phương thức tĩnh vào một lớp nơi chúng có thể ở một mình với dữ liệu mà chúng làm việc.

Tôi đã không ép buộc thực tập sinh của tôi sử dụng nó. Tôi chỉ cung cấp nó và để anh ta quyết định nếu anh ta muốn gắn bó với các phương thức tĩnh. Tôi về nhà nghĩ rằng anh ta có thể dính vào những gì anh ta đã làm việc. Ngày hôm sau tôi thấy anh ta đang sử dụng nó ở một loạt nơi. Nó tuyên bố phần còn lại của mã vẫn còn xấu xí và mang tính thủ tục, nhưng sự phức tạp này giờ đã bị ẩn khỏi chúng ta đằng sau một đối tượng. Nó đã tốt hơn một chút.

Bây giờ chắc chắn mỗi khi bạn truy cập nó là một công việc hợp lý. Một DTO là một giá trị lưu trữ nhanh đẹp. Tôi lo lắng về điều đó nhưng nhận ra rằng tôi có thể thêm bộ nhớ đệm nếu chúng tôi cần mà không cần chạm vào bất kỳ mã nào đang sử dụng. Vì vậy, tôi sẽ không làm phiền cho đến khi chúng tôi quan tâm.

Tôi có nói bạn nên luôn luôn bám sát các đối tượng OO trên DTO không? Không. DTO tỏa sáng khi bạn cần vượt qua một ranh giới ngăn bạn khỏi các phương pháp di chuyển. DTO có vị trí của họ.

Nhưng các đối tượng OO cũng vậy. Tìm hiểu làm thế nào để sử dụng cả hai công cụ. Tìm hiểu những gì từng chi phí. Tìm hiểu để cho vấn đề, tình hình và thực tập quyết định. Giáo điều không phải là bạn của bạn ở đây.


Vì câu trả lời của tôi đã quá dài một cách nực cười, hãy để tôi không cho bạn hiểu về một số quan niệm sai lầm với việc xem xét mã của bạn.

Ví dụ, một lớp thường có các thành viên và phương thức của lớp, ví dụ:

public class Cat{
    private String name;
    private int weight;
    private Image image;

    public void printInfo(){
        System.out.println("Name:"+this.name+",weight:"+this.weight);
    }

    public void draw(){
        //some draw code which uses this.image
    }
}

Nhà xây dựng của bạn ở đâu? Điều này không cho tôi thấy đủ để biết nó có hữu ích không.

Nhưng sau khi đọc về nguyên tắc trách nhiệm đơn và nguyên tắc đóng mở, tôi thích tách một lớp thành DTO và lớp trình trợ giúp chỉ với các phương thức tĩnh, ví dụ:

public class CatData{
    public String name;
    public int weight;
    public Image image;
}

public class CatMethods{
    public static void printInfo(Cat cat){
        System.out.println("Name:"+cat.name+",weight:"+cat.weight);
    }

    public static void draw(Cat cat){
        //some draw code which uses cat.image
    }
}

Tôi nghĩ rằng nó phù hợp với nguyên tắc trách nhiệm duy nhất bởi vì bây giờ trách nhiệm của CatData là chỉ giữ dữ liệu, không quan tâm đến các phương thức (cũng đối với CatMethods).

Bạn có thể làm nhiều điều ngớ ngẩn nhân danh Nguyên tắc Trách nhiệm duy nhất. Tôi có thể lập luận rằng Cat String và Cat ints nên được tách ra. Các phương thức vẽ và Hình ảnh đó đều phải có lớp riêng. Rằng chương trình đang chạy của bạn là một trách nhiệm duy nhất nên bạn chỉ nên có một lớp. : P

Đối với tôi, cách tốt nhất để tuân theo Nguyên tắc Trách nhiệm duy nhất là tìm ra một sự trừu tượng hóa tốt cho phép bạn đặt sự phức tạp vào một hộp để bạn có thể che giấu nó. Nếu bạn có thể đặt cho nó một cái tên hay khiến mọi người không khỏi ngạc nhiên bởi những gì họ tìm thấy khi họ nhìn vào bên trong thì bạn đã theo dõi nó khá tốt. Mong đợi nó sẽ ra lệnh nhiều quyết định hơn thì đó là yêu cầu rắc rối. Thành thật mà nói, cả hai danh sách mã của bạn đều làm như vậy nên tôi không hiểu tại sao SRP lại quan trọng ở đây.

Và nó cũng phù hợp với nguyên tắc đóng mở vì thêm các phương thức mới không cần thay đổi lớp CatData.

Ồ không. Nguyên tắc đóng mở không phải là về việc thêm các phương thức mới. Đó là về việc có thể thay đổi việc thực hiện các phương thức cũ và không phải chỉnh sửa gì cả. Không có gì sử dụng bạn và không phải phương pháp cũ của bạn. Thay vào đó bạn viết một số mã mới ở một nơi khác. Một số hình thức đa hình sẽ làm điều đó độc đáo. Đừng thấy điều đó ở đây.

Câu hỏi của tôi là, nó là một mô hình tốt hay chống?

Làm thế nào tôi nên biết? Hãy nhìn, làm một trong hai cách có lợi ích và chi phí. Khi bạn tách mã khỏi dữ liệu, bạn có thể thay đổi mà không phải biên dịch lại mã khác. Có lẽ đó là rất quan trọng đối với bạn. Có lẽ nó chỉ làm cho mã của bạn trở nên phức tạp vô nghĩa.

Nếu nó làm cho bạn cảm thấy tốt hơn thì bạn không ở xa thứ mà Martin Fowler gọi là một đối tượng tham số . Bạn không phải chỉ đưa người nguyên thủy vào đối tượng của bạn.

Những gì tôi muốn bạn làm là phát triển ý thức về cách thực hiện sự tách biệt của bạn, hoặc không, theo phong cách mã hóa. Bởi vì tin hay không bạn không bị buộc phải chọn một phong cách. Bạn chỉ cần sống với sự lựa chọn của bạn.


2

Bạn đã vấp phải một chủ đề tranh luận đang tạo ra tranh luận giữa các nhà phát triển trong hơn một thập kỷ. Năm 2003, Martin Fowler đã đặt ra cụm từ "Mô hình miền thiếu máu" (ADM) để mô tả sự phân tách dữ liệu và chức năng này. Anh ta - và những người khác đồng ý với anh ta - lập luận rằng "Mô hình miền phong phú" (trộn dữ liệu và chức năng) là "OO thích hợp" và cách tiếp cận ADM là một mô hình chống OO.

Luôn có những người bác bỏ lập luận này và khía cạnh của lập luận đó ngày càng lớn mạnh hơn trong những năm gần đây với sự chấp nhận của nhiều nhà phát triển kỹ thuật phát triển chức năng. Cách tiếp cận đó tích cực khuyến khích việc phân tách dữ liệu và các mối quan tâm chức năng. Dữ liệu phải là bất biến càng nhiều càng tốt, vì vậy việc đóng gói trạng thái đột biến trở thành một vấn đề không đáng lo ngại. Không có lợi ích nào khi gắn các chức năng trực tiếp vào dữ liệu đó trong các tình huống như vậy. Cho dù sau đó "không phải OO" hay không hoàn toàn không được người dân như vậy quan tâm.

Bất kể bạn ngồi ở phía nào của hàng rào (Tôi ngồi rất chắc chắn trên "Martin Fowler đang nói về một BTW cũ"), việc bạn sử dụng các phương pháp tĩnh cho printInfodrawgần như không được chấp nhận. Phương pháp tĩnh khó chế giễu khi viết bài kiểm tra đơn vị. Vì vậy, nếu chúng có tác dụng phụ (chẳng hạn như in hoặc vẽ lên màn hình hoặc thiết bị khác), chúng không nên tĩnh hoặc nên có vị trí đầu ra được truyền vào dưới dạng tham số.

Vì vậy, bạn có thể có một giao diện:

public interface CatMethods {
    void printInfo(Cat cat);
    void draw(Cat cat);
}

Và một triển khai được đưa vào phần còn lại của hệ thống của bạn trong thời gian chạy (với các triển khai khác đang được sử dụng để thử nghiệm):

internal class CatMethodsForScreen implements CatMethods {
    public void printInfo(Cat cat) {
        System.out.println("Name:"+cat.name+",weight:"+cat.weight);
    }

    public void draw(Cat cat) {
        //some draw code which uses cat.image
    }
}

Hoặc thêm các tham số bổ sung để loại bỏ các tác dụng phụ từ các phương thức đó:

public static class CatMethods {
    public static void printInfo(Cat cat, OutputHandler output) {
        output.println("Name:"+cat.name+",weight:"+cat.weight);
    }

    public static void draw(Cat cat, Canvas canvas) {
        //some draw code which uses cat.image and draws it on canvas
    }
}

Nhưng tại sao bạn lại cố gắng biến một ngôn ngữ OO như Java thành ngôn ngữ chức năng, thay vì sử dụng ngôn ngữ chức năng ngay từ đầu? Đây có phải là một loại "Tôi ghét Java, nhưng mọi người đều sử dụng nó nên tôi phải làm vậy, nhưng ít nhất tôi sẽ viết nó theo cách riêng của mình". Trừ khi bạn cho rằng mô hình OO đã lỗi thời và lỗi thời theo một cách nào đó. Tôi đăng ký vào trường "Nếu bạn muốn X, bạn biết nơi để có được nó".
Kayaman

@Kayaman, " Tôi đăng ký" Nếu bạn muốn X, bạn biết nơi để có được nó "trường phái suy nghĩ ". Tôi không. Tôi thấy thái độ đó vừa thiển cận vừa hơi khó chịu. Tôi không sử dụng Java, nhưng tôi sử dụng C # và tôi bỏ qua một số tính năng "OO thực". Tôi không quan tâm đến sự kế thừa, các đối tượng trạng thái và những thứ tương tự và tôi viết rất nhiều phương thức tĩnh và các lớp niêm phong (cuối cùng). Công cụ và quy mô cộng đồng xung quanh C # là không ai sánh kịp. Đối với F #, đó là cách nghèo hơn. Vì vậy, tôi đăng ký vào trường "Nếu bạn muốn X, hãy yêu cầu thêm nó vào trường tư duy công cụ hiện tại của bạn".
David Arno

1
Tôi muốn nói rằng bỏ qua các khía cạnh của một ngôn ngữ khác rất nhiều so với việc cố gắng trộn và kết hợp các tính năng từ các ngôn ngữ khác. Tôi cũng không cố viết "OO thực", vì cố gắng tuân theo các quy tắc (hoặc nguyên tắc) trong một khoa học không chính xác như lập trình không hoạt động. Tất nhiên bạn cần biết các quy tắc để bạn có thể phá vỡ chúng. Việc tấn công vào các tính năng một cách mù quáng có thể có hại cho sự phát triển của ngôn ngữ. Không có ý xúc phạm, nhưng IMHO một phần của cảnh FP dường như cảm thấy "FP là điều tuyệt vời nhất kể từ khi cắt lát bánh mì, tại sao mọi người sẽ không hiểu nó". Tôi sẽ không bao giờ nói (hoặc nghĩ) OO hoặc Java là "tốt nhất".
Kayaman

1
@Kayaman, nhưng "cố gắng trộn và kết hợp các tính năng từ các ngôn ngữ khác" nghĩa là gì? Bạn có cho rằng các tính năng chức năng không nên được thêm vào java, bởi vì "Java là ngôn ngữ OO"? Nếu vậy, đó là một lập luận thiển cận trong quan điểm của tôi. Hãy để ngôn ngữ phát triển với mong muốn của các nhà phát triển. Tôi buộc họ phải chuyển sang ngôn ngữ khác thay vào đó là cách tiếp cận sai, tôi cảm thấy. Chắc chắn rằng một số "người ủng hộ FP" vượt lên trên về việc FP là điều tốt nhất từ ​​trước đến nay. Và một số "người ủng hộ OO" vượt lên trên về việc OO đã "chiến thắng trận chiến vì nó rõ ràng vượt trội". Bỏ qua cả hai.
David Arno

1
Tôi không chống FP. Tôi sử dụng nó trong Java, nơi nó hữu ích. Nhưng nếu một lập trình viên nói "Tôi muốn điều này" thì giống như một khách hàng nói "Tôi muốn điều này". Các lập trình viên không phải là nhà thiết kế ngôn ngữ và khách hàng không phải là lập trình viên. Tôi nêu lên câu trả lời của bạn vì bạn nói rằng "giải pháp" của OP được tán thành. Nhận xét trong câu hỏi ngắn gọn hơn, tức là anh ta đã phát minh lại chương trình thủ tục bằng ngôn ngữ OO. Ý tưởng chung có một điểm mặc dù, như bạn đã chỉ ra. Một mô hình miền không thiếu máu không có nghĩa là dữ liệu và hành vi là độc quyền và các trình kết xuất hoặc trình bày bên ngoài sẽ bị cấm.
Kayaman

0

DTOs - Đối tượng truyền dữ liệu

rất hữu ích cho việc đó Nếu bạn định chuyển dữ liệu giữa các chương trình hoặc hệ thống thì DTOs mong muốn vì chúng cung cấp một tập hợp con có thể quản lý được của một đối tượng chỉ liên quan đến định dạng và cấu trúc dữ liệu. Ưu điểm là bạn không phải đồng bộ hóa các bản cập nhật cho các phương thức phức tạp trên một số hệ thống (miễn là dữ liệu cơ bản không thay đổi).

Toàn bộ quan điểm của OO là liên kết dữ liệu và mã tác động lên dữ liệu đó chặt chẽ với nhau. Tách một đối tượng logic thành các lớp riêng biệt thường là một ý tưởng tồi.


-1

Nhiều mẫu thiết kế và nguyên tắc mâu thuẫn, và cuối cùng chỉ có phán đoán của bạn là một lập trình viên giỏi sẽ giúp bạn quyết định những mẫu nào nên áp dụng cho một vấn đề cụ thể.

Theo tôi, nguyên tắc làm điều đơn giản nhất có thể làm việc có thể giành chiến thắng theo mặc định trừ khi bạn có lý do mạnh mẽ để làm cho thiết kế phức tạp hơn. Vì vậy, trong ví dụ cụ thể của bạn, tôi sẽ chọn thiết kế đầu tiên, đó là một cách tiếp cận hướng đối tượng truyền thống hơn với dữ liệu và các chức năng cùng nhau trong cùng một lớp.

Dưới đây là một vài câu hỏi bạn có thể tự hỏi về ứng dụng:

  • Tôi có bao giờ cần cung cấp một cách khác để hiển thị hình ảnh Cat không? Nếu vậy, thừa kế sẽ không phải là một cách thuận tiện để làm điều đó?
  • Lớp Cat của tôi có cần kế thừa từ lớp khác hay đáp ứng giao diện không?

Trả lời có cho bất kỳ câu hỏi nào trong số này có thể đưa bạn đến một thiết kế phức tạp hơn với các lớp chức năng và DTO riêng biệt.


-1

Đó có phải là một mô hình tốt?

Có lẽ bạn sẽ nhận được câu trả lời khác nhau ở đây bởi vì mọi người theo các trường phái khác nhau. Vì vậy, trước khi tôi bắt đầu: Câu trả lời này là theo lập trình hướng đối tượng như được mô tả bởi Robert C. Martin trong cuốn sách "Clean Code".


"Đúng" OOP

Theo như tôi biết, có 2 cách hiểu về OOP. Đối với hầu hết mọi người, nó hiểu rõ "một đối tượng là một lớp".

Tuy nhiên, tôi thấy trường phái tư tưởng khác hữu ích hơn: "Đối tượng là thứ gì đó làm gì đó". Nếu bạn đang làm việc với các lớp, mỗi lớp sẽ là một trong hai điều: " người giữ dữ liệu " hoặc một đối tượng . Chủ sở hữu dữ liệu giữ dữ liệu (duh), các đối tượng làm công cụ. Điều đó không có nghĩa là người giữ dữ liệu không thể có phương pháp! Hãy nghĩ về một list: A listkhông thực sự làm bất cứ điều gì, nhưng nó có phương pháp, để thực thi cách nó giữ dữ liệu .

Chủ sở hữu dữ liệu

Bạn Catlà một ví dụ điển hình của người giữ dữ liệu. Chắc chắn, bên ngoài chương trình của bạn, một con mèo là một cái gì đó làm một cái gì đó , nhưng bên trong chương trình của bạn, Catmột chuỗi là một chuỗi name, một int weightvà một hình ảnh image.

Những người nắm giữ dữ liệu không làm gì cũng không có nghĩa là họ không chứa logic kinh doanh! Nếu Catlớp của bạn có một nhà xây dựng yêu cầu name, weightimage, bạn đã gói gọn thành công quy tắc kinh doanh mà mọi con mèo đều có. Nếu bạn có thể thay đổi weightsau khi một Catlớp được tạo, đó là một quy tắc kinh doanh khác được gói gọn. Đây là những điều rất cơ bản, nhưng điều đó chỉ có nghĩa là điều đó rất quan trọng để không hiểu sai - và theo cách này, bạn đảm bảo rằng không thể hiểu sai.

Các đối tượng

Đối tượng làm một cái gì đó. Nếu bạn muốn các đối tượng Clean *, chúng ta có thể thay đổi điều đó thành "Đối tượng làm một việc".

In thông tin con mèo là công việc của một đối tượng. Ví dụ: bạn có thể gọi đối tượng này là " CatInfoLogger", với một phương thức công khai Log(Cat). Đó là tất cả những gì chúng ta cần biết từ bên ngoài: Đối tượng này ghi thông tin của một con mèo và để làm như vậy, nó cần một Cat.

Ở bên trong, đối tượng này sẽ có các tham chiếu đến bất cứ điều gì nó cần để hoàn thành trách nhiệm duy nhất là đăng nhập mèo. Trong trường hợp này, đó chỉ là một tham chiếu Systemcho System.out.println, nhưng trong hầu hết các trường hợp, nó sẽ là các tham chiếu riêng đến các đối tượng khác. Nếu logic để định dạng đầu ra trước khi in nó trở nên quá phức tạp, bạn CatInfoLoggercó thể chỉ cần tham chiếu đến một đối tượng mới CatInfoFormatter. Nếu sau này, mỗi bản ghi cũng cần được ghi vào một tệp, bạn có thể thêm một CatToFileLoggerđối tượng thực hiện điều đó.

Chủ sở hữu dữ liệu so với đối tượng - tóm tắt

Bây giờ, tại sao bạn sẽ làm tất cả điều đó? Bởi vì bây giờ việc thay đổi một lớp chỉ thay đổi một điều ( cách con mèo được ghi lại trong ví dụ). Nếu bạn đang thay đổi một đối tượng, bạn chỉ thay đổi cách thực hiện một hành động nhất định; nếu bạn thay đổi một người giữ dữ liệu, bạn chỉ thay đổi dữ liệu nào được giữ và / hoặc theo cách nó được giữ.

Thay đổi dữ liệu có thể có nghĩa là cách thay đổi cũng phải thay đổi, nhưng thay đổi đó sẽ không xảy ra cho đến khi bạn tự thực hiện bằng cách thay đổi đối tượng chịu trách nhiệm.

Quá mức cần thiết?

Giải pháp này có vẻ hơi quá mức. Hãy để tôi thẳng thắn: Nó là . Nếu toàn bộ chương trình của bạn chỉ lớn như ví dụ, đừng thực hiện bất kỳ điều nào ở trên - chỉ cần chọn một trong những cách được đề xuất của bạn với việc tung đồng xu. Không có nguy cơ gây nhầm lẫn mã khác nếu có không có mã khác.

Tuy nhiên, bất kỳ phần mềm "nghiêm trọng" (= nhiều hơn một tập lệnh hoặc 2) có thể đủ lớn để nó có thể thu lợi từ một cấu trúc như thế này.


Câu trả lời

Việc tách hầu hết các lớp thành DTO và các lớp trợ giúp [...] là tốt hay chống mẫu?

Biến "hầu hết các lớp" (không có bất kỳ trình độ chuyên môn nào nữa) thành DTOs / chủ sở hữu dữ liệu, không phải là một mẫu chống, bởi vì thực tế nó không phải là bất kỳ mẫu nào - nó ít nhiều ngẫu nhiên.

Tuy nhiên, giữ dữ liệu và các đối tượng cách nhau được coi là một cách tốt để giữ cho mã của bạn sạch *. Đôi khi bạn sẽ chỉ cần một DTO phẳng, "thiếu máu" và đó là nó. Những lần khác, bạn cần các phương thức để thực thi cách giữ dữ liệu. Chỉ cần không đặt chức năng của chương trình của bạn với dữ liệu.


* Đó là "Sạch" với chữ C viết hoa như tiêu đề sách, bởi vì - hãy nhớ đoạn đầu tiên - đây là về một trường phái tư tưởng, trong khi đó cũng có những trường khác.

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.