使用哪种OO设计(是否有设计模式)?


11

我有两个代表“酒吧/俱乐部”(您喝酒/社交的地方)的对象。

在一种情况下,我需要栏名称,地址,距离,标语

在另一种情况下,我需要栏名称,地址,网站网址,徽标

因此,我有两个对象代表相同的事物,但具有不同的字段。

我喜欢使用不可变的对象,因此所有字段都是从构造函数设置的

一种选择是拥有两个构造函数,而其他字段为空,即:

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
}

我不喜欢这样,因为当您使用吸气剂时您将必须进行空检查

在我的真实示例中,第一个场景有3个字段,第二个场景有10个字段,所以拥有两个构造函数实在是一件很痛苦的事情,我必须声明为null的字段数量,然后在使用对象时,您不会不知道Bar您在哪里使用哪个字段,因此哪些字段为空而哪些字段为空。

我还有什么其他选择?

两个类称为BarPreviewBar

某种类型的继承/接口?

还有很棒的东西吗?


29
哇,您实际上想出了将Bar用作标识符的合法用途!
梅森惠勒

1
如果您共享某些属性,则一种选择是实现基类。
Yusubov

1
我从来没想过这点。为我的Bar / Foo狗店编写任何类型的代码都可能会造成混乱。
Erik Reppen


4
@gnat人们如何猜测。从您的链接引用中:You should only ask practical, answerable questions based on actual problems that you face.恰恰是这里发生的一切
Blundell

Answers:


9

我的想法:

在您的域中代表的“酒吧”在任何一个地方都有可能需要的所有东西:名称,地址,URL,徽标,标语和“距离”(我是从请求者的位置猜测)。因此,在您的域中,无论以后将在何处使用数据,都应有一个“ Bar”类是一个条的权威数据源。此类应该是可变的,以便可以在必要时对条形数据进行更改并保存。

但是,您有两个地方需要此Bar对象的数据,并且两个地方都只需要一个子集(并且您不希望更改该数据)。通常的答案是“数据传输对象”或DTO。一个包含不可变属性获取器的POJO(普通Java对象)。这些DTO可以通过在Bar主域对象上调用方法“ toScenario1DTO()”和“ toScenario2DTO()”来产生。结果是水合的DTO(这意味着您只需要在一个地方使用冗长而复杂的构造函数)。

如果您需要将数据发送回主域类(以进行更新;如果无法根据需要更改数据以反映现实世界的当前状态,那么数据的意义是什么?),您可以构造一个DTO,或使用新的可变DTO,然后使用“ updateFromDto()”方法将其交给Bar类。

编辑:提供一个示例:

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 }
}

Address,Distance和Url类本身应该是不可变的,或者在DTO中使用时,应将它们替换为不可变的对应物。


首字母缩写DTO代表什么?我不太明白您的意思,请您把它放在一个可行的例子中。fyi数据来自服务器,因此一旦此类的任何一种形式被“水合”,就无需更改字段,只需将其用于显示在UI上即可
-Blundell

1
DTO代表“数据传输对象”,指的是一种非常简单的结构的数据类,用于将数据从“丰富”域层移到像UI这样的上层,而不向UI公开实际的域层(允许更改)只要DTO不必更改,就可以在不影响UI的情况下将其应用于域。
KeithS 2012年

可变性与修改或持久性无关。

4
@JarrodRoberson-你在开玩笑吗?如果类是不可变的(在实例化后不能就地更改),则对数据层中的数据进行更改的唯一方法是构造一个新实例,该实例表示具有不同成员的相同记录(相同PK)。尽管产生新实例的“变异”方法可以使其变得更容易,但它仍然对修改和持久性有很大影响。
KeithS 2012年

1
@JarrodRoberson 听社区。你错了。。事实上一半在这整个的答案表明,我们需要一些基本的OO各地板上学的意见-这是令人厌恶..
大卫·考登

5

如果只关心属性的一个子集,并且想要确保它们不会混淆,请创建两个接口,然后使用该接口与基础对象进行通信。


1
你说,但你可以使用举一个例子Bar
布伦德尔

3

生成器模式(或接近它)可能会使用在这里。

拥有不可变的对象是一件令人钦佩的事情,但是现实是,使用Java中的Reflection,没有什么是真正安全的;-)。


我可以看到HawaiianPizzaBuilder它是如何工作的,因为它需要的值是硬编码的。但是,如果值被检索并传递给构造函数,如何使用此模式?该HawaiianPizzaBuilder会仍然具有与所有干将SpicyPizzaBuilder有这么空是可能的。除非将其与@Jarrods结合使用Null Object Pattern。一个带有的代码示例Bar将使您明白
Blundell

+1在这样的情况下,我使用的建设者,就像一个魅力-包括但不限于合理的设置默认值,而不是空时,我想这
蚊蚋

3

这里的关键是“ Bar”是什么以及在一个或另一个上下文中如何使用它之间的区别。

酒吧是现实世界(或人造世界,如游戏)中的单个实体,并且只能由一个对象实例表示。任何时候以后,当您不从代码段中创建该实例,而是从配置文件或数据库中加载该实例时,这一点将更加明显。

(更深奥的是:每个Bar实例的生命周期都与程序运行时代表该对象的对象不同。即使您有创建该实例的源代码,也意味着该Bar实体存在,“ ”在源代码中处于休眠状态,而在该代码实际在内存中创建时“唤醒” ...)

很抱歉,起步很长,但是我希望这可以使我明白。您拥有一个拥有所有需要的属性的Bar ,以及一个代表每个Bar实体的Bar 实例。这在您的代码中是正确的,并且与您希望在不同上下文中看到相同实例的方式无关

后者可以由两个不同的接口表示,它们包含所需的访问方法(getName(),getURL(),getDistance()),并且Bar类应同时实现两者。(也许“距离”将更改为“位置”,并且getDistance()成为另一个位置的计算:-))

但是创建的对象是Bar实体,而不是您要使用该实体的方式:一个构造函数,所有字段。

编辑:我可以编写代码!:-)

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

适当的模式

您正在寻找的东西通常被称为Null Object Pattern。如果您不喜欢该名称,则可以将其称为Undefined Value Pattern,相同语义的不同标签。有时将此模式称为Poison Pill Pattern

在所有这些情况下,对象都将是替换对象或代表a Default Value而不是null. It doesn't replace the semantic ofnull null but makes it easier to work with the data model in a more predictable way because现在不再是有效状态。

它是一种模式,在其中保留给定类的特殊实例以将其他null选项表示为Default Value。这样,您就不必检查null,您可以根据已知NullObject实例检查身份。您可以安全地在其上调用方法之类,而不必担心NullPointerExceptions

这样,您就可以null用其代表NullObject实例替换您的分配,并且完成了。

正确的面向对象分析

这样,您就可以拥有Interface多态性的共性,并且仍然可以不必担心接口的特定实现中是否缺少数据。因此,有些Bar可能没有网络状态,有些在构建时可能没有位置数据。Null Object Patter可让您为每个marker数据提供一个默认值,该默认值用于表示相同内容的数据,此处未提供任何内容,而无需处理NullPointerException整个地方。

正确的面向对象设计

首先有一个abstract实现,它是BarClub共享的所有属性的超集。

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
}

然后,您可以实现Establishment该类的子类,并为BarClub类中的每一个添加不适用于其他类的特定内容。

坚持不懈

这些占位符对象(如果构造正确)可以透明地存储在数据库中,而无需任何特殊处理。

未来证明

如果您决定稍后再使用Inversion of Control / Dependency Injection潮流,那么这种模式也使注入这些标记对象变得容易。


0

我认为问题在于您在这两种情况下都没有对Bar进行建模(而您正在对两个不同的问题,对象等进行建模)。如果我看到一个Bar类,我希望有一些与饮料,菜单,可用座位有关的功能,而您的对象则没有任何功能。如果我看到您的对象行为,那么您正在建模有关企业的信息。Bar是您当前正在使用的对象,但这不是它们正在实现的内在行为。(在另一种情况下:如果要为婚姻建模,则将有两个实例变量“人妻”;“人妻”;“妻子”是您当时赋予该对象的当前角色,但该对象仍然是“人”)。我会做这样的事情:

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

确实没有必要为此太复杂。您需要两种不同的对象吗?做两堂课。

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;
     }
     //...
}

如果您需要对它们进行同样的操作,请考虑添加接口或使用反射。


@David:哦,是的。他们有一个共同的整体数据成员。紧急!
DeadMG

如果没有一些通用的接口,则此解决方案中就不会存在多态性,在这种糟糕的设计决策下,这两个类都无法替代另一个。并不是说它们没有相同的属性超集,而是默认情况下,其中一些属性是null。记住null意味着没有数据,而不是没有属性。

1
@DeadMG这完全有可能是将共享值分组为父对象的想法的练习。如果您在这种情况下提出解决方案,您的解决方案将不会得到充分的认可。
大卫·考登

OP没有规定任何可替代性的需求。正如我说的,如果需要,您可以只添加一个接口或使用反射。
DeadMG

@DeadMG但是反射就像试图在一个环境中编写跨平台移动应用程序一样-可能有效,但这是不正确的。使用反射调用方法的代价比普通方法调用慢2到50倍。反思并不是万能的..
David Cowden

-1

对于此类问题,我的答案是将其分解为可管理的步骤

  1. 首先只需创建两个类,BarOne然后BarTwo(或Bar在不同的程序包中同时调用它们)
  2. 作为单独的类开始使用您的对象,现在不必担心代码重复。您会注意到当您从一种方法过渡到另一种方法(重复方法)时
  3. 您可能会发现它们之间没有任何关联,因此您应该问自己bar如果不是将有问题的类重命名为现在所代表的类,它们是否都是真的
  4. 如果找到共同的字段或行为,则可以提取interfacesuperclass具有共同的行为
  5. 一旦有了interfacesuperclass,就可以创建builderfactory来创建/检索实现对象

(该问题的其他答案是关于4和5)


-2

您需要一个基类,例如具有名称地址的Location。现在,您有两个BarBarPreview类扩展了基类Location。在每个类中,您都将初始化超类的公共变量,然后初始化您的唯一变量:

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;
    }
}

与BarPreview类类似。

如果可以让您晚上睡得更好,请用AnythingYouThinkWouldBeAnAppropriateNameForAThingThatABarExtendsSuchAsFoodServicePlace -ffs 替换我代码中所有Location实例。


OP希望实例是不可变的,这意味着一切都必须是final

1
这可能有效,您需要final输入@David字段。虽然Bar应该没有extend Location意义。也许Bar extends BaseBarBarPreview extends BaseBar这些名字并不真正听起来好得要么虽然,我希望的东西更优雅
布伦德尔

@JarrodRoberson我正在为他绘制草图。.添加final使其不可变。OP的问题的根本问题是,他不知道如何拥有一个基类和两个扩展基类的单独类。我只是在详细说明。
大卫·考登

@Blundell您在说什么?一个酒吧 一个位置。只需用BaseBar替换我的Location即可,这是完全一样的。.您知道,当一个类扩展另一个类时,它扩展的类不必命名为Base [ClassThatWillExtend],对吗?
大卫·考登

1
@DavidCowden,您可以停止在其他答案下方做为评论来宣传您的答案吗?
marktani 2012年
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.