【问题标题】:Is this a valid use of the builder pattern in Java (or even good OO design)?这是对 Java 中构建器模式的有效使用(甚至是良好的 OO 设计)吗?
【发布时间】:2012-11-19 22:32:18
【问题描述】:

作为 OO 的新手,我经常觉得自己理解了一个概念,直到我尝试从一个简化的示例转移到给我的实际需求。对于理解如何思考这个特定问题的任何帮助,我将不胜感激。

我有一个 GUI,它有一个面板,它定义了一个容器和其中的项目。目前,有三种类型的容器。容器具有一些属性(如大小),可以包含一到三种不同类型的项目(两种是可选的)。输入足够的信息后,我会使用这些信息制作图表。

我实现了一个观察者模式。当用户输入信息时,它会更新一个 observable,通知图它已经改变了。

到目前为止我很高兴。现在皱纹。我的容器有一个尺寸,但有时它是明确输入的,有时它是由容器所容纳的东西决定的。这取决于容器的类型。如果没有明确输入,如何确定大小取决于可选项目之一是否在容器中。我不确定需求编写者是否只是讨厌我,或者我缺乏足够的 OO 经验,但是这些皱纹让我很不舒服。现在,我的 observable 只有变量来保存所有分类信息,我使用一堆 switch 语句来处理特殊情况。

我认为我可以使用构建器模式。导演将产生被绘制的数据。我将为每种类型的容器都有一个具体的构建器,并且我将使用容器属性和其中的项目来实例化该类。我将有抽象构建器类的方法将图形所需的值返回给主管,例如 getContainerSize() 并将它们组合起来以产生实际的数据点。此外,如果用户还没有输入足够的数据来完成图表,director 可能会返回 null。

我是否正在接近可用的 OO 设计?我不确定我是不是把特殊的外壳埋得更深了。

另一个皱纹。其中一种物品类型放在所有三个容器中。现在,我的 observable 分别跟踪容器和项目,创建图表的方法决定了请求的内容(随着用户使用这些值,图表会发生很大变化)。如果我有多个构建器模式,那将如何工作?

也许我错过了一步? observable 更新当前容器的构建器,然后让图知道它应该调用 director 来获取它的坐标?那么哪个还需要询问当前容器是什么?

欢迎所有帮助我了解 OO 设计或特别是这个问题的 cmets。实际需求有更多的特殊情况,但都是在这个基本主题上的变化。

感谢您的回复。我认为我将两个问题混为一谈而感到内疚。这里尝试提供一个专注于 Builder 模式的最小代码示例。注意 IE8 我看不到任何标识,FireFox 8,对于任何阅读 IE8 代码的人,我深表歉意。

interface MyContainerBuilder
{
     void   setContents( MyContents contents );

     Double myVolume();
     Double myDensity();   
}

class SmallContainerBuilder implements MyContainerBuilder
{
    Double     volume   = null;
    Double     density  = null;
    MyContents contents = null;

    public void   setVolume()
    {
        if (contents != null)
        {
            volume = contents.myDensity() / 3.0;
        }
    }

    public void   setContents( MyContents contents )
    {
        this.contents = contents;
    }

    public Double myVolume()
    {
        if (volume == null)
            setVolume();
        return volume;
    }

    public Double myDensity()   
    {
        return contents.myDensity();
    }
}

class BigContainerBuilder implements MyContainerBuilder
{
    Double     volume   = null;
    Double     density  = null;
    MyContents contents = null;

    public void   setVolume( Double volume )
    {
        this.volume = volume;
    }

    public void   setContents( MyContents contents )
    {
        this.contents = contents;
    }

    public Double myVolume()
    {
        return volume;
    }

    public Double myDensity()   
    {
        return contents.myDensity();
    }
}

class ContainerDirector
{
    Double myResult( MyContainerBuilder container )
    {
        return container.myVolume() * container.myDensity();
    }
}

class MyContents
{
    Double density;

    MyContents( Double density )
    {
        this.density = density;
    }

    public Double myDensity()
    {
        return density;
    }
}

class Test
{
    public static void main(String[] args)
    {
        SmallContainerBuilder smallContainer = new SmallContainerBuilder();
        BigContainerBuilder   bigContainer   = new BigContainerBuilder();
        ContainerDirector     director       = new ContainerDirector();
//
// Assume this comes from the GUI, where an ActionListener knows which Builder
// to use based on the user's action. I'd be having my observable store this.
       Double        density       = 15.0;
       MyContents    contents      = new MyContents( density );
       smallContainer.setContents( contents );
//
// Then I would need to tell my observer to do this.
        Double       results       = director.myResult( smallContainer );
        System.out.println( "Use this result: " + results );
    }
}

我有两种类型的容器,它们使用不同的方法来计算体积。因此,假设我有单选按钮来选择容器类型,并且在每个单选按钮下都有一个可以进入所选容器的项目组合框。组合框上的 ActionListener 会将项目放入正确的容器中并将其保存到我的 observable 中(实际上还有很多其他的东西被设置),它告诉我的观察者使用 director 来获取适当的值,然后观察者更新GUI 的一些视图组件。

【问题讨论】:

  • TLDR; Builder 模式很适合用长/动态参数列表替换构造函数。
  • 也许有一些带有 cmets 的代码 sn-ps? (即,根据您已有的内容。它会帮助那些 Java 比英语强的人。)
  • 看看我添加的代码是否让事情更清楚,如果不是我会尝试进一步澄清。

标签: java design-patterns


【解决方案1】:

我的容器有一个尺寸,但有时它是明确输入的,有时它是由容器所装的东西决定的。这由容器的类型决定。 [...] 如果没有明确输入,则取决于其中一项可选项目是否在容器中。

听起来你可以有一个抽象容器的不同子类,每个子类都以不同的方式实现getContainerSize()。一种用于明确输入,一种用于有可选项目的情况,另一种用于没有它的情况。

...我使用一堆 switch 语句来处理特殊情况。

听起来不太好。 Replace Conditional with Polymorphism 如果适用。

我在想我可以使用构建器模式...

我假设您需要根据一组输入变量来确定对象的具体类型(或null)。该模式提供了一种构建复杂对象的方法,如果它知道这是什么类型,但实际的问题是决定哪种类型。所以你在某个地方需要条件代码。那个地方可以是建造者,但也可以是简单的工厂。

现在,我的 observable 分别跟踪容器和项目[...] observable 更新当前容器的构建器[...] 如果我有多个构建器模式,那将如何工作?

并不真正了解 Observable 正在观察什么以及在哪种情况下会触发什么变化,但是 Observable 更新一个(或多个)构建器听起来很奇怪。不过这更像是一种直觉:)

我是否正在接近可用的 OO 设计?

如果它有效,是的。但实际上我无法告诉您您是否创建了一个好的或可用的设计,因为我仍然不知道您的问题或您的设计的细节 - 在阅读了您的文字几次之后。

与其现在向您的问题添加另一页信息,不如尝试将您的问题分解为更小的部分,并使用代码 sn-ps/图像/图表或任何类型的可视化来帮助人们理解您的问题以及它们之间的所有联系那些碎片。只是大量的文本是相当可怕的,像这样一个巨大的 OO 设计对于 SO 来说整体来说太大而且太本地化了。


您的方法看起来不错,但它需要 IMO 相当复杂的对象来证明这种使用的合理性。

您通过观察者在您的 GUI 中创建一个 MyContents 实例。然后将该对象包装在 MyContainerBuilder 中,然后将其提供给 ContainerDirector ,然后生成结果。如果 MyContents 或结果很简单,我认为这一步太过分了。

另外,将 MyContents 设置为 MyContainerBuilder 的方式意味着您不能盲目地重用同一个具体的 MyContainerBuilder 实例。您要么必须确保按顺序使用它,要么每次都必须构建一个新的。

也就是说这不起作用

MyContents content1 = new MyContents( 5 );
MyContents content2 = new MyContents( 6 );
smallContainer.setContents( content1 );
smallContainer.setContents( content2 ); // overwriting old state
Double results1 = director.myResult( smallContainer ); // wrong result
Double results2 = director.myResult( smallContainer );

我假设 MyContents 是一个通用的数据保存对象,由用户在几个步骤中填充数据。一旦用户对它感到满意,它就会被提交以构建结果。据我所知,到时候你就知道结果是什么了。

下面是一种使用策略模式的方法(? - 我对所有这些名称和细微差别都不好),我选择直接插入 MyContents,因此一旦完成,MyContents 对象就包含了如何转换的所有细节成一个结果。这样可以确保一步,您不需要创建/维护额外的构建器对象。 MyContents 现在在某种程度上已经是 Builder。

interface VolumeStrategy {
     Double calculateVolume(Double density);
}
class SmallVolumeStrategy implements VolumeStrategy {
    public Double calculateVolume(Double density) {
        return density / 3.0;
    }
}
class BigVolumeStrategy implements VolumeStrategy {
    public Double calculateVolume(Double density) {
        return density;
    }
}

class ContainerDirector {
    Double myResult( MyContents container ) {
        Double density = container.myDensity();
        VolumeStrategy strategy = container.myStrategy();
        return density * strategy.calculateVolume(density);
    }
}

class MyContents {
    // built via observer
    Double density;
    MyContents( Double density ) {
        this.density = density;
    }
    public Double myDensity() {
        return density;
    }

    // plugged in at the end.
    VolumeStrategy strategy;
    public void setStrategy(VolumeStrategy strategy) {
        this.strategy = strategy;
    }
    public VolumeStrategy myStrategy() {
        return strategy;
    }
}

public class Test {
    public static void main(String[] args) {
        // all those can be static 
        VolumeStrategy       smallStrategy  = new SmallVolumeStrategy();
        VolumeStrategy       bigStratetgy   = new BigVolumeStrategy();
        ContainerDirector    director       = new ContainerDirector();

       // from the GUI
       Double        density       = 15.0;
       MyContents    contents      = new MyContents( density );
       // building this contents ...
       // ... time to submit, we know what strategy to use
       contents.setStrategy(smallStrategy);

       // can turn contents into result without needing to know anything about it.
        Double       results       = director.myResult( contents );
        System.out.println( "Use this result: " + results );
    }
}

我认为这种方式应该可以很好地解决我想象中的问题。我可能错了。

【讨论】:

  • @JonSwanson 也为您添加了一些内容。顺便说一句,我不是亲 OO 模式的架构师。不确定了解所有这些抽象 OO 原则的人是否会喜欢它:)
  • 这很有帮助。我同意我的方法让我觉得过于复杂,但我没有看到通往更简单方法的道路。让我感到困惑的是,我有一个包含两个主要部分的继承界面,但是有一些业务规则将它们链接在一起。我有两个类来表示容器和内容。我想在容器类中有容器的方法,在内容类中有内容的方法。但有时它的内容决定了容器的值。因此,我正在尝试为处理该问题的 observable 获取数据结构。
  • @JonSwanson “但有时它的内容决定了容器的值。”也许您需要考虑对内容/容器概念进行更根本的改变。就像为两者规定事情的第三类一样。继承的接口也一样,两个主要部分听起来像是不同的responsibility 合并为一件事。拆分、合并和洗牌,必须有一个好的解决方案。也许相关:sourcemaking.com/refactoring/duplicate-observed-data(它提到了 gui 和观察者!:))
  • 快速评论,我需要阅读您发送的链接。内容由多个可观察对象观察。除了一个之外,所有都只需要其中包含的内容对象和方法。我最初对构建器的尝试是包含容器和内容的第三类。只有一种类型的容器使用内容来定义自己。所以它将拥有自己的构建器实例。一旦定义(通过使用来自容器外部的数据运行方法),它在获得最终结果方面的行为就像所有其他容器一样。所以导演可以对每个构建器使用相同的方法调用。
猜你喜欢
  • 2010-10-22
  • 1970-01-01
  • 2013-05-14
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2011-08-16
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多