Thiết kế OO nào để sử dụng (có Mẫu thiết kế) không?


11

Tôi có hai đối tượng đại diện cho một 'Bar / Câu lạc bộ' (một nơi bạn uống / giao lưu).

Trong một kịch bản tôi cần tên thanh, địa chỉ, khoảng cách, khẩu hiệu

Trong một kịch bản khác, tôi cần tên thanh, địa chỉ, url trang web, logo

Vì vậy, tôi đã có hai đối tượng đại diện cho cùng một thứ nhưng với các lĩnh vực khác nhau.

Tôi thích sử dụng các đối tượng bất biến, vì vậy tất cả các trường được đặt từ hàm tạo .

Một tùy chọn là có hai hàm tạo và null các trường khác, ví dụ:

class Bar {
     private final String name;
     private final Distance distance;
     private final Url url;

     public Bar(String name, Distance distance){
          this.name = name;
          this.distance = distance;
          this.url = null;
     }

     public Bar(String name, Url url){
          this.name = name;
          this.distance = null;
          this.url = url;
     }

     // getters
}

Tôi không thích điều này vì bạn sẽ phải kiểm tra null khi bạn sử dụng getters

Trong ví dụ thực tế của tôi, kịch bản đầu tiên có 3 trường và kịch bản thứ hai có khoảng 10 trường, vì vậy sẽ là một nỗi đau thực sự khi có hai hàm tạo , số lượng trường tôi sẽ phải khai báo null và sau đó khi đối tượng được sử dụng, bạn sẽ không Tôi không biết Barbạn đang sử dụng ở đâu và vì vậy những lĩnh vực nào sẽ là null và những gì sẽ không.

Tôi có những lựa chọn nào khác?

Hai lớp gọi BarPreviewBar?

Một số loại kế thừa / giao diện?

Một cái gì đó khác là tuyệt vời?


29
Ồ, bạn thực sự đã nghĩ ra cách sử dụng hợp pháp Barlàm định danh!
Mason Wheeler

1
nếu bạn đang chia sẻ một số thuộc tính, một tùy chọn sẽ là triển khai một lớp cơ sở.
Yusubov

1
Tôi không bao giờ nghĩ về điều đó. Viết bất kỳ loại mã nào cho cửa hiệu chó Bar / Foo của tôi có thể thực sự khó hiểu.
Erik Reppen


4
@gnat Mọi người đoán thế nào. Từ trích dẫn liên kết của bạn: You should only ask practical, answerable questions based on actual problems that you face.và đó chính xác là những gì đang xảy ra ở đây
Blundell

Câu trả lời:


9

Suy nghĩ của tôi:

"Thanh", như được thể hiện trong miền của bạn, có tất cả những thứ có thể cần thiết ở một trong hai nơi: tên, địa chỉ, URL, logo, slogan và "khoảng cách" (tôi đoán từ vị trí của người yêu cầu). Do đó, trong miền của bạn, cần có một lớp "Thanh" là nguồn dữ liệu có thẩm quyền cho một thanh, bất kể dữ liệu sẽ được sử dụng ở đâu sau này. Lớp này phải có thể thay đổi, để thay đổi dữ liệu của thanh có thể được thực hiện và lưu lại khi cần thiết.

Tuy nhiên, bạn có hai vị trí cần có dữ liệu của đối tượng Bar này và cả hai chỉ cần một tập hợp con (và bạn không muốn dữ liệu đó bị thay đổi). Câu trả lời thông thường là "đối tượng truyền dữ liệu" hoặc DTO; một POJO (đối tượng Java đơn giản) có chứa các thuộc tính bất biến. Các DTO này có thể được tạo bằng cách gọi một phương thức trên đối tượng miền Bar chính: "toScenario1DTO ()" và "toScenario2DTO ()"; kết quả là một DTO ngậm nước (có nghĩa là bạn chỉ cần sử dụng hàm tạo dài, phức tạp ở một nơi).

Nếu bạn cần gửi dữ liệu trở lại lớp miền chính (để cập nhật nó; điểm dữ liệu là gì nếu bạn không thể thay đổi dữ liệu khi cần để phản ánh trạng thái hiện tại của thế giới thực?), Bạn có thể xây dựng một trong những Các DTO hoặc sử dụng một DTO có thể thay đổi mới và đưa nó trở lại lớp Bar bằng phương thức "updateFromDto ()".

EDIT: để cung cấp một ví dụ:

public class Bar {
     private String name;
     private Address address; 
     private Distance distance;
     private Url url;
     private Image logo;
     private string Slogan;

     public OnlineBarDto ToOnlineDto()
     {
         return new OnlineBarDto(name, address, url, logo);
     }

     public PhysicalBarDto ToPhysicalDto()
     {
         return new PhysicalBarDto(name, address, distance, slogan);
     }

     public void UpdateFromDto(PhysicalBarDto dto)
     {
         //validation logic here, or mixed into assignments

         name = dto.Name;
         address = dto.Address;
         distance = dto.Distance;
         slogan = dto.Slogan;
     }

     public void UpdateFromDto(OnlineBarDto dto)
     {
         //Validate DTO fields before performing assignments

         name = dto.Name;
         address = dto.Address;
         url= dto.Url;
         logo = dto.Logo;
     }

     // getters/setters - As necessary within the model and data access layers;
     // other classes can update the model using DTOs, forcing validation.
}

public class PhysicalBarDto
{
     public final String Name;
     public final Address Address;
     public final Distance Distance;
     public final String Slogan;

     public PhysicalBarDto(string Name, Address address, Distance distance, string slogan) 
     { //set instance fields using parameter fields; you know the drill }
}

public class OnlineBarDto
{
     public final String Name;
     public final Address Address;
     public final Image Logo;
     public final Url Url;

     public OnlineBarDto(string Name, Address address, Url url, Image logo) 
     { //ditto }
}

Các lớp Địa chỉ, Khoảng cách và Url nên là bất biến, hoặc khi được sử dụng trong DTO, chúng nên được thay thế bằng các đối tác bất biến.


từ viết tắt DTO có nghĩa là gì? Tôi hoàn toàn không hiểu những gì bạn nói, bạn có thể đưa nó vào một ví dụ hoạt động được không. fyi Dữ liệu đến từ một máy chủ nên một khi một trong hai dạng của lớp này bị 'ngậm nước', các trường sẽ không cần phải thay đổi, chỉ được sử dụng để hiển thị trên UI
Blundell

1
DTO là viết tắt của "đối tượng truyền dữ liệu" và dùng để chỉ một lớp dữ liệu có cấu trúc rất đơn giản được sử dụng để di chuyển dữ liệu từ lớp miền "giàu" ra các lớp trên như UI, mà không để lộ lớp miền thực cho UI (cho phép thay đổi được tạo cho miền mà không ảnh hưởng đến giao diện người dùng miễn là DTO không phải thay đổi).
KeithS

khả năng biến đổi không có liên quan gì đến việc sửa đổi hoặc kiên trì.

4
@JarrodRoberson - bạn đang đùa à? Nếu một lớp là bất biến (không thể thay đổi tại chỗ sau khi khởi tạo), cách duy nhất để thực hiện thay đổi dữ liệu trong lớp dữ liệu là xây dựng một thể hiện mới biểu thị cùng một bản ghi (cùng PK) với các thành viên khác nhau. Mặc dù các phương thức "đột biến" tạo ra một thể hiện mới có thể làm cho nó dễ dàng hơn, nhưng nó vẫn có một ảnh hưởng rất lớn đến sự sửa đổi và kiên trì.
KeithS

1
@JarrodRoberson Lắng nghe cộng đồng. Bạn sai rồi. . Trên thực tế, một nửa số ý kiến ​​trong toàn bộ câu trả lời này cho thấy rằng chúng ta cần một số trường học OO cơ bản xung quanh hội đồng quản trị - thật kinh tởm ..
David Cowden

5

Nếu bạn chỉ quan tâm đến một tập hợp con của các thuộc tính và bạn muốn chắc chắn rằng chúng không bị lẫn lộn, hãy tạo hai giao diện và sử dụng giao diện đó để nói chuyện với đối tượng cơ sở của bạn.


1
Bạn nói điều đó nhưng bạn có thể đưa ra một ví dụ bằng cách sử dụng Barlớp học
Blundell

3

Mẫu Builder (hoặc một cái gì đó gần với nó) có thể được sử dụng ở đây.

Có các đối tượng bất biến là một điều đáng ngưỡng mộ, nhưng thực tế là với Reflection trong Java, không có gì thực sự an toàn ;-).


Tôi có thể thấy cách thức HawaiianPizzaBuilderhoạt động vì các giá trị nó cần được mã hóa cứng. Tuy nhiên, làm thế nào bạn có thể sử dụng mẫu này nếu các giá trị được lấy và chuyển cho một hàm tạo? Các HawaiianPizzaBuildervẫn sẽ có tất cả các getters rằng SpicyPizzaBuildercó rất rỗng là có thể. Trừ khi bạn kết hợp điều này với @Jarrods Null Object Pattern. Một ví dụ mã Barsẽ giúp bạn hiểu ý
Blundell

+1 Tôi sử dụng trình xây dựng trong các trường hợp như thế này, hoạt động như một bùa mê - bao gồm, nhưng không giới hạn ở việc đặt mặc định hợp lý thay vì null khi tôi muốn điều này
gnat

3

Điểm mấu chốt ở đây là sự khác biệt giữa "Thanh" là gìcách bạn sử dụng nó trong một hoặc một bối cảnh khác.

Bar là một thực thể duy nhất trong thế giới thực (hoặc thế giới nhân tạo, giống như một trò chơi) và chỉ có một thể hiện đối tượng nên đại diện cho nó. Bất cứ lúc nào sau này, khi bạn không tạo cá thể đó từ một đoạn mã, nhưng tải nó từ tệp cấu hình hoặc cơ sở dữ liệu, điều này sẽ rõ ràng hơn.

. "ở trạng thái không hoạt động trong mã nguồn của bạn và" đánh thức "khi mã đó thực sự tạo ra nó trong bộ nhớ ...)

Xin lỗi vì sự khởi đầu dài, nhưng tôi hy vọng điều này làm cho quan điểm của tôi rõ ràng. Bạn có lớp ONE Bar có tất cả các thuộc tính mà bạn cần và một thể hiện Bar đại diện cho mỗi thực thể Bar. Điều này đúng trong mã của bạn và độc lập với cách bạn muốn xem cùng một thể hiện trong các bối cảnh khác nhau .

Cái sau có thể được biểu diễn bằng hai giao diện khác nhau , chứa các phương thức truy cập bắt buộc (getName (), getURL (), getDistance ()) và lớp Bar nên thực hiện cả hai. (Và có lẽ "khoảng cách" sẽ thay đổi thành "vị trí" và getDistance () trở thành phép tính từ một vị trí khác :-))

Nhưng việc tạo ra là dành cho thực thể Bar chứ không phải cho cách bạn muốn sử dụng thực thể đó: một hàm tạo, tất cả các trường.

EDITED: Tôi có thể viết mã! :-)

public interface Place {
  String getName();
  Address getAddress();
}

public interface WebPlace extends Place {
   URL getUrl();
   Image getLogo();
}

public interface PhysicalPlace extends Place {
  Double getDistance();
  Slogon getSlogon();
}

public class Bar implements WebPlace, PhysicalPlace {
  private final String name;
  private final Address address;
  private final URL url;
  private final Image logo;
  private final Double distance;
  private final Slogon slogon;

  public Bar(String name, Address address, URL url, Image logo, Double distance, Slogon slogon) {
    this.name = name;
    this.address = address;
    this.url = url;
    this.logo = logo;
    this.distance = distance;
    this.slogon = slogon;
  }

  public String getName() { return name; }
  public Address getAddress() { return address; }
  public Double getDistance() { return distance; }
  public Slogon getSlogon() { return slogon; }
  public URL getUrl() { return url; }
  public Image getLogo() { return logo; } 
}

1

Mẫu phù hợp

Những gì bạn đang tìm kiếm thường được gọi là Null Object Pattern. Nếu bạn không thích cái tên đó, bạn có thể gọi nó là Undefined Value Patterncùng một nhãn ngữ nghĩa khác nhau. Đôi khi mô hình này được gọi Poison Pill Pattern.

Trong tất cả những trường hợp này, đối tượng là một sự thay thế hoặc đứng cho một Default Valuethay vì null. It doesn't replace the semantic ofbut makes it easier to work with the data model in a more predictable way becausenull` nên bây giờ không bao giờ là một nhà nước hợp lệ.

Đó là một Mẫu mà bạn dành một thể hiện đặc biệt của một lớp nhất định để thể hiện một nulltùy chọn khác là a Default Value. Bằng cách này, bạn không phải kiểm tra lại null, bạn có thể kiểm tra danh tính đối với NullObjecttrường hợp đã biết của bạn . Bạn có thể gọi các phương thức trên đó một cách an toàn và tương tự mà không phải lo lắng NullPointerExceptions.

Bằng cách này, bạn thay thế nullbài tập của mình bằng các NullObjecttrường hợp đại diện của họ và bạn đã hoàn thành.

Phân tích định hướng đối tượng phù hợp

Bằng cách này, bạn có thể có điểm chung Interfacecho đa hình và vẫn có thể bảo vệ khỏi phải lo lắng về việc không có dữ liệu trong các triển khai cụ thể của giao diện. Vì vậy, một số Barcó thể không có sự hiện diện của web và một số có thể không có dữ liệu vị trí tại thời điểm xây dựng. Null Object Pattercho phép bạn cung cấp một giá trị mặc định cho mỗi markerdữ liệu đó là dữ liệu có cùng nội dung, không có gì được cung cấp ở đây, mà không phải đối phó với việc kiểm tra NullPointerExceptionmọi nơi.

Thiết kế hướng đối tượng phù hợp

Đầu tiên có một abstracttriển khai là một tập hợp tất cả các thuộc tính mà cả hai BarClubchia sẻ.

class abstract Establishment 
{
     private final String name;
     private final Distance distance;
     private final Url url;

     public Bar(final String name, final Distance distance, final Url url)
     {
          this.name = name;
          this.distance = distance;
          this.url = url;
     }

     public Bar(final String name, final Distance distance)
     {
          this(name, distance, Url.UNKOWN_VALUE);
     }

     public Bar(final String name, final Url url)
     {
          this(name, Distance.UNKNOWN_VALUE, url);
     }

     // other code
}

Sau đó, bạn có thể triển khai các lớp con của Establishmentlớp này và chỉ thêm những thứ cụ thể bạn cần cho từng lớp BarClubcác lớp không áp dụng cho lớp kia.

Kiên trì

Các đối tượng giữ chỗ này nếu được xây dựng chính xác có thể được lưu trữ trong cơ sở dữ liệu mà không cần xử lý đặc biệt.

Bằng chứng tương lai

Nếu bạn quyết định nhảy vào băng thông Inversion of Control / Dependency Injection sau đó, mẫu này sẽ giúp bạn dễ dàng tiêm các đối tượng đánh dấu này.


0

Tôi nghĩ vấn đề là bạn không lập mô hình một Bar trong một trong hai trường hợp đó (Và bạn đang mô hình hóa hai vấn đề khác nhau, các đối tượng, v.v.). Nếu tôi thấy một Bar Class tôi sẽ mong đợi một số chức năng liên quan đến đồ uống, thực đơn, chỗ ngồi có sẵn và đối tượng của bạn không có cái nào trong số đó. Nếu tôi thấy hành vi đối tượng của bạn, bạn đang lập mô hình thông tin về một cơ sở. Thanh là những gì bạn đang sử dụng cho những lúc này, nhưng thực chất đó không phải là hành vi mà họ đang thực hiện. . Tôi sẽ làm một cái gì đó như thế này:

class EstablishmentInformation {
     private final String name;

     public EstablishmentInformation(String name){
          this.name = name;
     }

     // getters
}

class EstablishmentLocationInformation {
    EstablishmentInformation establishmentInformation;
     private final Distance distance;

     public EstablishmentLocationInformation (String name, Distance distance){
          this.establishmentInformation = new EstablishmentInformation(name)
          this.distance = distance;
     }
}

class EstablishmentWebSiteInformation {
    EstablishmentInformation establishmentInformation;
     private final Url url;

     public EstablishmentWebSiteInformation(String name, Url url){
          this.establishmentInformation = new EstablishmentInformation(name)
          this.url = url;
     }
}

-1

Thực sự không cần phải quá phức tạp điều này. Bạn cần hai loại đối tượng khác nhau? Làm hai lớp.

class OnlineBar {
     private final String name;
     private final Url url;
     public OnlineBar(String name, Url url){
          this.name = name;
          this.url = url;
     }

     // ...
}
class PhysicalBar {
     private final String name;
     private final Distance distance;
     public PhysicalBar(String name, Distance distance){
          this.name = name;
          this.distance = distance;
     }
     //...
}

Nếu bạn cần thao tác trên chúng như nhau, hãy xem xét thêm giao diện hoặc sử dụng sự phản chiếu.


@David: Ôi trời ơi. Họ có thích, một thành viên toàn bộ dữ liệu chung. TRƯỜNG HỢP KHẨN CẤP!
DeadMG

không có một số giao diện chung thì không có tính đa hình trong giải pháp này, cả hai lớp này đều không thể thay thế cho lớp kia với quyết định thiết kế kém này. Không phải là họ không có cùng các thuộc tính, mà theo mặc định, một số thuộc tính đó là null. Ghi nhớ nullcó nghĩa là sự vắng mặt của dữ liệu , không phải là sự vắng mặt của thuộc tính.

1
@DeadMG Đây hoàn toàn có thể là một bài tập trong chính xác ý tưởng nhóm các giá trị được chia sẻ vào các đối tượng cha mẹ .. giải pháp của bạn sẽ không nhận được tín dụng đầy đủ nếu bạn đề xuất nó trong bối cảnh đó.
David Cowden

OP không chỉ định bất kỳ nhu cầu thay thế. Và như tôi đã nói, bạn chỉ có thể thêm một giao diện hoặc sử dụng sự phản chiếu nếu bạn muốn.
DeadMG

@DeadMG Nhưng sự phản chiếu giống như cố gắng viết một ứng dụng di động đa nền tảng bằng một môi trường - Nó có thể hoạt động, nhưng nó không chính xác . Hình phạt cho việc gọi một phương thức sử dụng phản xạ là chậm hơn từ 2 đến 50 lần so với cách gọi phương thức thông thường. Suy ngẫm không phải là thuốc chữa bệnh ..
David Cowden

-1

Câu trả lời của tôi cho bất cứ ai có loại vấn đề này là chia nó thành các bước có thể quản lý được .

  1. Đầu tiên chỉ cần tạo hai lớp BarOneBarTwo(hoặc gọi cả hai Barnhưng trong các gói khác nhau)
  2. Bây giờ hãy bắt đầu sử dụng các đối tượng của bạn vì các lớp riêng biệt không lo lắng về việc sao chép mã. Bạn sẽ nhận thấy khi bạn vượt qua (phương thức trùng lặp) từ phương thức này sang phương pháp khác
  3. Bạn có thể phát hiện ra rằng chúng không liên quan đến nhau, và vì vậy bạn nên tự hỏi cả hai có thực sự là mộtbar nếu không đổi tên lớp vi phạm thành những gì nó hiện đại diện
  4. Nếu bạn tìm thấy các trường hoặc hành vi chung, bạn có thể trích xuất một interfacehoặc superclassvới hành vi chung
  5. Khi bạn có interfacehoặc superclassbạn có thể tạo builderhoặc factoryđể tạo / truy xuất các đối tượng triển khai của mình

(4 và 5 là những gì các câu trả lời khác cho câu hỏi này là về)


-2

Bạn cần một lớp cơ sở, ví dụ: Vị trítênđịa chỉ . Bây giờ, bạn có hai lớp BarBarPreview mở rộng Vị trí lớp cơ sở . Trong mỗi lớp, bạn khởi tạo các biến chung siêu lớp và sau đó là các biến duy nhất của bạn:

public class Location {
    protected final String name;
    protected final String address:

    public Location (String locName, String locAddress) {
    name = locName;
    address = locAddress
    }

}

public class Bar extends Location {
    private final int dist;
    private final String slogan;

    public Bar(String barName, String barAddress,
               int distance, String barSlogan) {
    super(locName, locAddress);
    dist = distance;
    slogan = barSlogan;
    }
}

Và tương tự cho lớp BarPreview ..

Nếu điều đó khiến bạn ngủ ngon hơn vào ban đêm, hãy thay thế tất cả các trường hợp Vị trí trong mã của tôi bằng AnythingYouThinkWouldBeAnAppropTHERNameForAT BreathThatABarExtendsSuchAsFoodServicePlace - ffs.


OP muốn thể hiện là bất biến , điều đó có nghĩa là mọi thứ phải như vậy final.

1
Điều này có thể hoạt động, bạn cần đến finalcác trường @David. BarKhông nên extend Locationmặc dù điều đó không có ý nghĩa. Có lẽ Bar extends BaseBarBarPreview extends BaseBarnhững cái tên này cũng không thực sự nghe hay lắm, tôi đã hy vọng điều gì đó thanh lịch hơn
Blundell

@JarrodRoberson Tôi chỉ phác thảo nó cho anh ta .. chỉ cần thêm cuối cùng cho nó là bất biến. Đó là một người không có trí tuệ .. Vấn đề cơ bản với câu hỏi của OP là anh ta không biết làm thế nào để có một lớp cơ sở và hai lớp riêng biệt mở rộng một lớp cơ sở. Tôi chỉ đơn giản là chi tiết rằng.
David Cowden

@Blundell bạn đang nói về cái gì trên thế giới? Một quán bar một địa điểm . Chỉ cần thay thế Vị trí của tôi bằng BaseBar của bạn và đó chính là điều tương tự .. Bạn có biết rằng khi một lớp mở rộng một lớp khác, lớp đó sẽ không phải là Base [ClassThatWillExtend] phải không?
David Cowden

1
@DavidCowden, bạn có thể dừng lại để quảng cáo câu trả lời của bạn dưới dạng một bình luận bên dưới mỗi câu trả lời khác không?
marktani
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.