it-swarm-vi.com

Có bất kỳ lý do để sử dụng các lớp "dữ liệu cũ đơn giản"?

Trong mã kế thừa, tôi thỉnh thoảng thấy các lớp không có gì ngoài các hàm bao cho dữ liệu. cái gì đó như:

class Bottle {
   int height;
   int diameter;
   Cap capType;

   getters/setters, maybe a constructor
}

Sự hiểu biết của tôi về OO là các lớp là cấu trúc cho dữ liệu và các phương thức hoạt động trên dữ liệu đó. Điều này dường như loại trừ các đối tượng thuộc loại này. Đối với tôi chúng không là gì cả nhiều hơn structs và loại đánh bại mục đích của OO. Tôi không nghĩ nó nhất thiết là xấu xa, mặc dù nó có thể là mùi mã.

Có một trường hợp mà các đối tượng như vậy sẽ là cần thiết? Nếu điều này được sử dụng thường xuyên, nó có làm cho thiết kế nghi ngờ?

44
Michael K

Chắc chắn không phải là xấu xa và không phải là một mùi mã trong tâm trí của tôi. Các thùng chứa dữ liệu là một công dân hợp lệ OO. Đôi khi bạn muốn đóng gói các thông tin liên quan lại với nhau. Sẽ tốt hơn rất nhiều khi có một phương thức như

public void DoStuffWithBottle(Bottle b)
{
    // do something that doesn't modify Bottle, so the method doesn't belong
    // on that class
}

hơn

public void DoStuffWithBottle(int bottleHeight, int bottleDiameter, Cap capType)
{
}

Sử dụng một lớp cũng cho phép bạn thêm một tham số bổ sung vào Chai mà không sửa đổi mọi người gọi của DoStuffWithBottle. Và bạn có thể phân lớp Chai và tăng thêm khả năng đọc và tổ chức mã của bạn, nếu cần.

Ví dụ, cũng có các đối tượng dữ liệu đơn giản có thể được trả về do truy vấn cơ sở dữ liệu. Tôi tin rằng thuật ngữ cho họ trong trường hợp đó là "Đối tượng truyền dữ liệu".

Trong một số ngôn ngữ cũng có những cân nhắc khác. Ví dụ, trong các lớp C # và các cấu trúc hoạt động khác nhau, vì các cấu trúc là một loại giá trị và các lớp là các loại tham chiếu.

68
Adam Lear

Các lớp dữ liệu có giá trị trong một số trường hợp. DTO là một ví dụ điển hình được đề cập bởi Anna Lear. Tuy nhiên, nói chung, bạn nên coi chúng là hạt giống của một lớp mà các phương thức chưa được nảy mầm. Và nếu bạn đang chạy vào rất nhiều trong số chúng trong mã cũ, hãy coi chúng như một mùi mã mạnh. Chúng thường được sử dụng bởi các lập trình viên C/C++ cũ, những người chưa bao giờ thực hiện chuyển đổi sang OO và là một dấu hiệu của lập trình thủ tục. Dựa vào getters và setters mọi lúc (hoặc tệ hơn nữa là truy cập trực tiếp của các thành viên không riêng tư) có thể khiến bạn gặp rắc rối trước khi bạn biết điều đó. Hãy xem xét một ví dụ về phương pháp bên ngoài cần thông tin từ Bottle.

Ở đây Bottle là một lớp dữ liệu):

void selectShippingContainer(Bottle bottle) {
    if (bottle.getDiameter() > MAX_DIMENSION || bottle.getHeight() > MAX_DIMENSION ||
            bottle.getCapType() == Cap.FANCY_CAP ) {
        shippingContainer = WOODEN_CRATE;
    } else {
        shippingContainer = CARDBOARD_BOX;
    }
}

Ở đây chúng tôi đã đưa ra Bottle một số hành vi):

void selectShippingContainer(Bottle bottle) {
    if (bottle.isBiggerThan(MAX_DIMENSION) || bottle.isFragile()) {
        shippingContainer = WOODEN_CRATE;
    } else {
        shippingContainer = CARDBOARD_BOX;
    }
}

Phương thức đầu tiên vi phạm nguyên tắc Nói-Không-Hỏi, và bằng cách giữ Bottle chúng tôi đã để cho kiến ​​thức ngầm về các chai, chẳng hạn như điều gì làm cho một người yếu đuối (Cap), trượt vào logic nằm ngoài lớp Bottle. Bạn phải luôn tự tin để ngăn chặn loại 'rò rỉ' này khi bạn thường xuyên dựa vào getters.

Phương thức thứ hai yêu cầu Bottle chỉ cho những gì nó cần để thực hiện công việc của mình và để lại Bottle để quyết định xem nó có dễ vỡ hay lớn hơn kích thước đã cho hay không. Kết quả là một sự kết hợp lỏng lẻo hơn nhiều giữa phương pháp và việc triển khai của Chai. Một tác dụng phụ dễ chịu là phương pháp sạch hơn và biểu cảm hơn.

Bạn sẽ hiếm khi sử dụng nhiều trường này của một đối tượng mà không viết một số logic phải nằm trong lớp với các trường đó. Chỉ ra logic đó là gì và sau đó di chuyển nó đến nơi nó thuộc về.

25
Mike E

Nếu đây là thứ bạn cần, thì tốt, nhưng làm ơn, làm ơn, làm ơn làm đi

public class Bottle {
    public int height;
    public int diameter;
    public Cap capType;

    public Bottle(int height, int diameter, Cap capType) {
        this.height = height;
        this.diameter = diameter;
        this.capType = capType;
    }
}

thay vì một cái gì đó như

public class Bottle {
    private int height;
    private int diameter;
    private Cap capType;

    public Bottle(int height, int diameter, Cap capType) {
        this.height = height;
        this.diameter = diameter;
        this.capType = capType;
    }

    public int getHeight() {
        return height;
    }

    public void setHeight(int height) {
        this.height = height;
    }

    public int getDiameter() {
        return diameter;
    }

    public void setDiameter(int diameter) {
        this.diameter = diameter;
    }

    public Cap getCapType() {
        return capType;
    }

    public void setCapType(Cap capType) {
        this.capType = capType;
    }
}

Xin vui lòng.

7
compman

Như @Anna đã nói, chắc chắn không xấu xa. Chắc chắn bạn có thể đặt các hoạt động (phương thức) vào các lớp, nhưng chỉ khi bạn muốn đến. Bạn không đến.

Cho phép tôi hiểu một chút về ý tưởng rằng bạn phải đưa các hoạt động vào các lớp, cùng với ý tưởng rằng các lớp là trừu tượng. Trong thực tế, điều này khuyến khích các lập trình viên

  1. Tạo nhiều lớp hơn mức cần thiết (cấu trúc dữ liệu dự phòng). Khi một cấu trúc dữ liệu chứa nhiều thành phần hơn mức tối thiểu cần thiết, nó không được chuẩn hóa và do đó chứa các trạng thái không nhất quán. Nói cách khác, khi nó bị thay đổi, nó cần được thay đổi ở nhiều nơi để duy trì sự nhất quán. Việc không thực hiện tất cả các thay đổi phối hợp làm cho nó không nhất quán và là một lỗi.

  2. Giải quyết vấn đề 1 bằng cách đưa vào thông báo phương thức, để nếu phần A được sửa đổi, nó cố gắng truyền bá các thay đổi cần thiết cho phần B và C. Đây là lý do chính tại sao nên sử dụng get-and phương pháp truy cập -set. Vì đây là cách thực hành được khuyến nghị, nên nó xuất hiện để giải thích vấn đề 1, gây ra nhiều vấn đề hơn 1 và nhiều giải pháp 2. Kết quả này không chỉ xảy ra lỗi do thực hiện không đầy đủ các thông báo, mà còn là vấn đề giảm hiệu năng của thông báo chạy trốn. Đây không phải là những tính toán vô hạn, chỉ đơn thuần là những tính toán rất dài.

Những khái niệm này được dạy là những điều tốt, thường là bởi những giáo viên chưa từng làm việc trong các ứng dụng quái vật hàng triệu câu đố với những vấn đề này.

Đây là những gì tôi cố gắng làm:

  1. Giữ dữ liệu càng chuẩn hóa càng tốt, để khi thay đổi dữ liệu được thực hiện, nó được thực hiện ở càng ít điểm mã càng tốt, để giảm thiểu khả năng vào trạng thái không nhất quán.

  2. Khi dữ liệu phải không được chuẩn hóa và không thể tránh khỏi tình trạng dư thừa, không sử dụng thông báo trong nỗ lực để giữ cho dữ liệu nhất quán. Thay vào đó, chịu đựng sự không nhất quán tạm thời. Giải quyết sự không nhất quán với quét định kỳ thông qua dữ liệu bằng một quy trình chỉ thực hiện điều đó. Điều này tập trung trách nhiệm duy trì tính nhất quán, đồng thời tránh các vấn đề về hiệu suất và tính chính xác mà các thông báo dễ xảy ra. Điều này dẫn đến mã nhỏ hơn nhiều, không có lỗi và hiệu quả.

6
Mike Dunlavey

Với thiết kế trò chơi, đôi khi có 1000 cuộc gọi chức năng và người nghe sự kiện đôi khi có thể khiến nó đáng để có các lớp chỉ lưu trữ dữ liệu và có các lớp khác lặp qua tất cả các lớp chỉ có dữ liệu để thực hiện logic.

3
AttackingHobo

Đồng ý với Anna Lear,

Chắc chắn không phải là xấu xa và không phải là một mùi mã trong tâm trí của tôi. Các thùng chứa dữ liệu là một công dân hợp lệ OO. Đôi khi bạn muốn đóng gói các thông tin liên quan lại với nhau. Sẽ tốt hơn rất nhiều khi có một phương pháp như ...

Đôi khi mọi người quên đọc 1999 Java Các quy ước mã hóa làm cho nó rất đơn giản rằng loại lập trình này hoàn toàn tốt. Thực tế nếu bạn tránh nó, thì mã của bạn sẽ có mùi! (Quá nhiều getters/setters)

Từ Java Quy ước mã 1999: Một ví dụ về các biến thể hiện công khai thích hợp là trường hợp lớp về cơ bản là cấu trúc dữ liệu, không có hành vi. Nói cách khác , nếu bạn đã sử dụng một cấu trúc thay vì một lớp (if Java struct được hỗ trợ), thì nó phù hợp để biến các biến đối tượng của lớp thành công khai. - http://www.Oracle.com/technetwork/Java/javase/documentation/codeconventions-137265.html#177

Khi được sử dụng một cách chính xác, các POD (cấu trúc dữ liệu cũ đơn giản) tốt hơn POJO giống như POJO thường tốt hơn EJB.
[.__.] http://en.wikipedia.org/wiki/Plain_Old_Data_St cấu trúc

3
Richard Faber

Loại lớp này khá hữu ích khi bạn đang xử lý các ứng dụng cỡ trung/lớn, vì một số lý do:

  • thật dễ dàng để tạo một số trường hợp thử nghiệm và đảm bảo dữ liệu phù hợp.
  • nó chứa tất cả các loại hành vi liên quan đến thông tin đó, vì vậy thời gian theo dõi lỗi dữ liệu được giảm
  • Sử dụng chúng nên giữ cho phương pháp args nhẹ.
  • Khi sử dụng ORM, các lớp này mang lại sự linh hoạt và nhất quán. Thêm một thuộc tính phức tạp được tính toán dựa trên thông tin đơn giản đã có trong lớp, nối lại bằng cách viết một phương thức đơn giản. Điều này khá nhanh nhẹn và hiệu quả hơn khi phải kiểm tra cơ sở dữ liệu của bạn và đảm bảo tất cả các cơ sở dữ liệu được vá với sửa đổi mới.

Vì vậy, để tóm tắt, theo kinh nghiệm của tôi, chúng thường hữu ích hơn là gây phiền nhiễu.

3
guiman

Các cấu trúc có vị trí của chúng, ngay cả trong Java. Bạn chỉ nên sử dụng chúng nếu hai điều sau đây là đúng:

  • Bạn chỉ cần tổng hợp dữ liệu không có bất kỳ hành vi nào, ví dụ: để vượt qua như một tham số
  • Không quan trọng một loại giá trị nào mà dữ liệu tổng hợp có

Nếu đây là trường hợp, thì bạn nên đặt các trường công khai và bỏ qua các getters/setters. Getters và setters dù sao cũng rất cục mịch và Java là ngớ ngẩn vì không có các thuộc tính như một ngôn ngữ hữu ích.

Tuy nhiên, nếu một trong hai không áp dụng, bạn đang giao dịch với một lớp thực sự. Điều đó có nghĩa là tất cả các lĩnh vực nên riêng tư. (Nếu bạn thực sự cần một trường ở phạm vi dễ truy cập hơn, hãy sử dụng getter/setter.)

Để kiểm tra xem cấu trúc giả định của bạn có hành vi hay không, hãy xem khi các trường được sử dụng. Nếu nó có vẻ vi phạm nói, đừng hỏi , thì bạn cần chuyển hành vi đó vào lớp của mình.

Nếu một số dữ liệu của bạn không nên thay đổi, thì bạn cần phải làm cho tất cả các trường đó là cuối cùng. Bạn có thể cân nhắc việc tạo lớp của mình không thay đổi . Nếu bạn cần xác thực dữ liệu của mình, sau đó cung cấp xác thực trong setters và constructor. (Một mẹo hữu ích là xác định một setter riêng và sửa đổi trường của bạn trong lớp chỉ bằng cách sử dụng setter đó.)

Ví dụ Chai của bạn rất có thể sẽ thất bại cả hai bài kiểm tra. Bạn có thể có mã (giả) giống như thế này:

public double calculateVolumeAsCylinder(Bottle bottle) {
    return bottle.height * (bottle.diameter / 2.0) * Math.PI);
}

Thay vào đó nên

double volume = bottle.calculateVolumeAsCylinder();

Nếu bạn thay đổi chiều cao và đường kính, nó sẽ là cùng một chai? Chắc là không. Những người nên được cuối cùng. Là một giá trị âm ok cho đường kính? Chai của bạn phải cao hơn chiều rộng? Cap có thể là null? Không? Làm thế nào bạn xác nhận điều này? Giả sử khách hàng là ngu ngốc hoặc xấu xa. ( Không thể nói sự khác biệt. ) Bạn cần kiểm tra các giá trị này.

Đây là lớp Chai mới của bạn có thể trông như thế nào:

public class Bottle {

    private final int height, diameter;

    private Cap capType;

    public Bottle(final int height, final int diameter, final Cap capType) {
        if (diameter < 1) throw new IllegalArgumentException("diameter must be positive");
        if (height < diameter) throw new IllegalArgumentException("bottle must be taller than its diameter");

        setCapType(capType);
        this.height = height;
        this.diameter = diameter;
    }

    public double getVolumeAsCylinder() {
        return height * (diameter / 2.0) * Math.PI;
    }

    public void setCapType(final Cap capType) {
        if (capType == null) throw new NullPointerException("capType cannot be null");
        this.capType = capType;
    }

    // potentially more methods...

}
3
Eva

IMHO thường không có đủ các lớp như thế này trong các hệ thống hướng đối tượng nặng nề. Tôi cần phải cẩn thận đủ điều kiện đó.

Tất nhiên, nếu các trường dữ liệu có phạm vi và khả năng hiển thị rộng, điều đó có thể cực kỳ không mong muốn nếu có hàng trăm hoặc hàng ngàn vị trí trong cơ sở mã của bạn giả mạo dữ liệu đó. Đó là yêu cầu rắc rối và khó khăn để duy trì bất biến. Tuy nhiên, điều đó không có nghĩa là mọi lớp đơn lẻ trong toàn bộ cơ sở mã đều có lợi từ việc che giấu thông tin.

Nhưng có nhiều trường hợp các trường dữ liệu như vậy sẽ có phạm vi rất hẹp. Một ví dụ rất đơn giản là lớp Node riêng của cấu trúc dữ liệu. Nó thường có thể đơn giản hóa mã rất nhiều bằng cách giảm số lượng tương tác đối tượng đang diễn ra nếu nói Node có thể chỉ đơn giản bao gồm dữ liệu thô. Điều đó phục vụ như một cơ chế tách rời vì phiên bản thay thế có thể yêu cầu ghép hai chiều từ, giả sử, Tree->NodeNode->Tree trái ngược với chỉ đơn giản là Tree->Node Data.

Một ví dụ phức tạp hơn sẽ là các hệ thống thành phần thực thể như thường được sử dụng trong các công cụ trò chơi. Trong những trường hợp đó, các thành phần thường chỉ là dữ liệu thô và các lớp giống như dữ liệu bạn đã hiển thị. Tuy nhiên, phạm vi/khả năng hiển thị của chúng có xu hướng bị giới hạn do thường chỉ có một hoặc hai hệ thống có thể truy cập loại thành phần cụ thể đó. Kết quả là bạn vẫn có xu hướng thấy khá dễ dàng để duy trì bất biến trong các hệ thống đó và hơn nữa, các hệ thống như vậy có rất ít object->object tương tác, giúp dễ dàng hiểu được những gì đang diễn ra ở tầm mắt của con chim.

Trong những trường hợp như vậy, bạn có thể kết thúc với một cái gì đó giống như tương tác này (sơ đồ này biểu thị các tương tác, không phải khớp nối, vì một sơ đồ khớp nối có thể bao gồm các giao diện trừu tượng cho hình ảnh thứ hai bên dưới):

enter image description here

... trái ngược với điều này:

enter image description here

... Và loại hệ thống trước đây có xu hướng dễ bảo trì hơn rất nhiều và lý do về tính chính xác mặc dù thực tế là các phụ thuộc đang thực sự chảy vào dữ liệu. Bạn nhận được ít khớp nối hơn chủ yếu vì nhiều thứ có thể được chuyển thành dữ liệu thô thay vì các đối tượng tương tác với nhau tạo thành một biểu đồ tương tác rất phức tạp.

0
user204677