当前位置: 首页 > 面试题库 >

生成器模式和继承

陈胤
2023-03-14
问题内容

我的对象层次结构随着继承树的加深而增加了复杂性。这些都不是抽象的,因此,它们的所有实例都或多或少地达到了目的。

由于参数数量很多,我想使用“构建器模式”来设置属性,而不是对多个构造函数进行编码。由于我需要适应所有排列,因此继承树中的叶类将具有伸缩构造函数。

当我在设计过程中遇到一些问题时,我已经浏览了这里的答案。首先,让我给您一个简单的,简短的示例来说明问题。

public class Rabbit
{
    public String sex;
    public String name;

    public Rabbit(Builder builder)
    {
        sex = builder.sex;
        name = builder.name;
    }

    public static class Builder
    {
        protected String sex;
        protected String name;

        public Builder() { }

        public Builder sex(String sex)
        {
            this.sex = sex;
            return this;
        }

        public Builder name(String name)
        {
            this.name = name;
            return this;
        }

        public Rabbit build()
        {
            return new Rabbit(this);
        }
    }
}

public class Lop extends Rabbit
{
    public float earLength;
    public String furColour;

    public Lop(LopBuilder builder)
    {
        super(builder);
        this.earLength = builder.earLength;
        this.furColour = builder.furColour;
    }

    public static class LopBuilder extends Rabbit.Builder
    {
        protected float earLength;
        protected String furColour;

        public LopBuilder() { }

        public Builder earLength(float length)
        {
            this.earLength = length;
            return this;
        }

        public Builder furColour(String colour)
        {
            this.furColour = colour;
            return this;
        }

        public Lop build()
        {
            return new Lop(this);
        }
    }
}

现在我们有一些代码可以继续进行,我想构建一个图像Lop

Lop lop = new Lop.LopBuilder().furColour("Gray").name("Rabbit").earLength(4.6f);

此调用将无法编译,因为无法解析最后一个链接的调用,Builder未定义method
earLength。因此,这种方式要求将所有调用按特定的顺序链接起来,这是非常不切实际的,尤其是对于深层次树而言。

现在,在寻找答案的过程中,我遇到了一个JavaBuilder类的子类,该类]建议使用Curiously Recursive Generic
Pattern
。但是,由于我的层次结构不包含抽象类,因此该解决方案对我不起作用。但是这种方法依靠抽象和多态来起作用,这就是为什么我不相信自己可以适应我的需求的原因。

我目前使用的一种方法是覆盖Builder层次结构中超类的所有方法,并简单地执行以下操作:

public ConcreteBuilder someOverridenMethod(Object someParameter)
{
    super(someParameter);
    return this;
}

通过这种方法,我可以确保我可以返回一个可以发出链调用的实例。虽然这不像伸缩反模式那样糟糕,但它紧随其后,我认为它有点“ hacky”。

对于我不知道的问题,还有其他解决方法吗?优选地,与设计模式一致的解决方案。谢谢!


问题答案:

对于递归绑定,这当然是可能的,但是子类型构建器也必须是通用的,并且您需要一些临时抽象类。这有点麻烦,但是它仍然比非通用版本容易。

/**
 * Extend this for Mammal subtype builders.
 */
abstract class GenericMammalBuilder<B extends GenericMammalBuilder<B>> {
    String sex;
    String name;

    B sex(String sex) {
        this.sex = sex;
        return self();
    }

    B name(String name) {
        this.name = name;
        return self();
    }

    abstract Mammal build();

    @SuppressWarnings("unchecked")
    final B self() {
        return (B) this;
    }
}

/**
 * Use this to actually build new Mammal instances.
 */
final class MammalBuilder extends GenericMammalBuilder<MammalBuilder> {
    @Override
    Mammal build() {
        return new Mammal(this);
    }
}

/**
 * Extend this for Rabbit subtype builders, e.g. LopBuilder.
 */
abstract class GenericRabbitBuilder<B extends GenericRabbitBuilder<B>>
        extends GenericMammalBuilder<B> {
    Color furColor;

    B furColor(Color furColor) {
        this.furColor = furColor;
        return self();
    }

    @Override
    abstract Rabbit build();
}

/**
 * Use this to actually build new Rabbit instances.
 */
final class RabbitBuilder extends GenericRabbitBuilder<RabbitBuilder> {
    @Override
    Rabbit build() {
        return new Rabbit(this);
    }
}

有一种避免使用“具体”叶子类的方法,如果有的话:

class MammalBuilder<B extends MammalBuilder<B>> {
    ...
}
class RabbitBuilder<B extends RabbitBuilder<B>>
        extends MammalBuilder<B> {
    ...
}

然后,您需要使用菱形创建新实例,并在引用类型中使用通配符:

static RabbitBuilder<?> builder() {
    return new RabbitBuilder<>();
}

之所以可行,是因为type变量上的边界确保了eg的所有方法RabbitBuilder都具有带有的返回类型RabbitBuilder,即使type参数只是一个通配符。

不过,我不太喜欢它,因为您需要在各处使用通配符,并且只能使用菱形或原始类型创建新实例。我想您无论哪种方式都会有点尴尬。

顺便说一下,关于这一点:

@SuppressWarnings("unchecked")
final B self() {
    return (B) this;
}

有一种避免这种未经检查的强制转换的方法,那就是使方法抽象:

abstract B self();

然后在叶子子类中重写它:

@Override
RabbitBuilder self() { return this; }

这样做的问题在于,尽管类型更安全,但子类可以返回以外的值this。基本上,无论哪种方式,子类都有机会做一些错误的事情,因此我真的没有太多理由偏爱其中一种方法。



 类似资料:
  • 问题内容: 我想将一个Web服务URL请求表示为一个对象,并发现在继承层次结构中可能有很多通用参数。一个请求可以有很多参数,一些是必选参数,而其他是可选参数,我相信Bloch的Builder模式是一个不错的选择,它可以使用流畅的接口模拟命名参数。 具体来说,我正在设计Google Maps网络服务API,该API具有常规的网络服务请求 和是必填参数,也是必填参数。还有一个可选参数。 每个服务都有其

  • 问题 你需要准备一个复杂的、多部分的对象,你希望操作不止一次或有不同的配置。 解决方案 创建一个生成器封装对象的产生过程。 Todo.txt 格式提供了一个先进的但还是纯文本的方法来维护待办事项列表。手工输入每个项目有损耗且容易出错,然而 TodoTxtBuilder 类可以解决我们的麻烦: class TodoTxtBuilder constructor: (defaultParamet

  • 亦称: 建造者模式、Builder 意图 生成器模式是一种创建型设计模式, 使你能够分步骤创建复杂对象。 该模式允许你使用相同的创建代码生成不同类型和形式的对象。 问题 假设有这样一个复杂对象, 在对其进行构造时需要对诸多成员变量和嵌套对象进行繁复的初始化工作。 这些初始化代码通常深藏于一个包含众多参数且让人基本看不懂的构造函数中; 甚至还有更糟糕的情况, 那就是这些代码散落在客户端代码的多个位置

  • 由于无法解析最后一个链接调用,未定义方法,因此此调用将不会编译。所以这种方式要求所有调用都以特定的顺序链接起来,这是非常不切实际的,特别是对于一个深度层次结构树。 现在,在我寻找答案的过程中,我遇到了一个Java Builder类的子类,它建议使用奇怪的递归泛型模式。但是,由于我的层次结构不包含一个抽象类,所以这个解决方案对我不起作用。但是这种方法依赖于抽象和多态性来发挥作用,这就是为什么我不相信

  • 5. 生成器(Builder) Intent 封装一个对象的构造过程,并允许按步骤构造。 Class Diagram Implementation 以下是一个简易的 StringBuilder 实现,参考了 JDK 1.8 源码。 // java public class AbstractStringBuilder { protected char[] value; protec

  • 构建器模式使用简单对象并使用逐步方法构建复杂对象。 这种类型的设计模式属于创建模式,因为此模式提供了创建对象的最佳方法之一。 Builder类逐步构建最终对象。 此构建器独立于其他对象。 实现 (Implementation) 我们考虑过快餐店的商业案例,其中典型的餐点可以是汉堡包和冷饮。 汉堡可以是蔬菜汉堡或鸡肉汉堡,将由包装纸包装。 冷饮可以是可乐或百事可乐,也可以装在一个瓶子里。 我们将创建