设计模式入门-生成器


一、生成器是什么?

生成器是一种创建型设计模式,使你能够分步骤创建复杂对象。该模式允许你使用相同的创建 代码生成不同类型和形式的对象。

二、问题

假设有这样一个复杂对象,在对其进行构造时需要对诸多成员变量和嵌套对象进行繁复的初始化工作。这些初始化代码通常深藏于一个包含众多参数且让人基本看不懂的构造函数中;甚至还有更糟糕的情况,那就是这些代码散落在客户端代码的多个位置。

例如,我们来思考如何创建一个 房屋 House 对象。建造一栋简单的房屋,首先你需要建造四面墙和地板,安装房门和一套窗户,然后再建造一个屋顶。但是如果你想要一栋更宽敞更明亮的房屋,还要有院子和其他设施(例如暖气、排水和供电设备),那又该怎么办呢?

最简单的方法是扩展 房屋 基类,然后创建一系列涵盖所有参数组合的子类。但最终你将面对相当数量的子类。任何新增的参数(例如门廊类型)都会让这个层次结构更加复杂。另一种方法则无需生成子类。你可以在 房屋 基类中创建一个包括所有可能参数的超级构造函数,并用它来控制房屋对象。这种方法确实可以避免生成子类,但它却会造成另外一个问题。

拥有大量输入参数的构造函数也有缺陷:这些参数也不是每次都要全部用上的。通常情况下,绝大部分的参数都没有使用,这使得对于构造函数的调用十分不简洁。例如,只有很少的房子有游泳池,因此与游泳池相关的参数十之八九是毫无用处的。

三、解决方案

生成器模式建议将对象构造代码从产品类中抽取出来,并将其放在一个名为生成器的独立对象中。

该 模 式 会 将 对 象 构 造 过 程 划 分 为 一 组 步 骤, 比 如buildWalls 创建墙壁 和 buildDoor 创建房门 创建房门等。每次创建对象时,你都需要通过生成器对象执行一系列步骤。重点在于你无需调用所有步骤,而只需调用创建特定对象配置所需的那些步骤即可。

当你需要创建不同形式的产品时,其中的一些构造步骤可能需要不同的实现。例如,木屋的房门可能需要使用木头制造,而城堡的房门则必须使用石头制造。

在这种情况下,你可以创建多个不同的生成器,用不同方式实现一组相同的创建步骤。然后你就可以在创建过程中使用这些生成器(例如按顺序调用多个构造步骤)来生成不同类型的对象。

例如,假设第一个建造者使用木头和玻璃制造房屋,第二个建造者使用石头和钢铁, 而第三个建造者使用黄金和钻石。在调用同一组步骤后, 第一个建造者会给你一栋普通房屋,第二个会给你一座小城堡,而第三个则会给你一座宫殿。但是,只有在调用构造步骤的客户端代码可以通过通用接口与建造者进行交互时,这样的调用才能返回需要的房屋。

主管
你可以进一步将用于创建产品的一系列生成器步骤调用抽取成为单独的主管类。主管类可定义创建步骤的执行顺序,而生成器则提供这些步骤的实现。

主管知道需要哪些创建步骤才能获得可正常使用的产品。

严格来说,你的程序中并不一定需要主管类。客户端代码可直接以特定顺序调用创建步骤。不过,主管类中非常适合放入各种例行构造流程,以便在程序中反复使用。此外,对于客户端代码来说,主管类完全隐藏了产品构造细节。客户端只需要将一个生成器与主管类关联,然后使用主管类来构造产品,就能从生成器处获得构造结果了。

四、角色

  1. 生成器(Builder) 接口声明在所有类型生成器中通用的产品
    构造步骤。
  2. 具体生成器(Concrete Builders) 提供构造过程的不同实现。具体生成器也可以构造不遵循通用接口的产品。
  3. **产品(Products)**是最终生成的对象。由不同生成器构造的产品无需属于同一类层次结构或接口。
  4. **主管(Director)**类定义调用构造步骤的顺序,这样你就可以创建和复用特定的产品配置。
  5. 客户端(Client) 必须将某个生成器对象与主管类关联。一般情况下,你只需通过主管类构造函数的参数进行一次性关联即可。此后主管类就能使用生成器对象完成后续所有的构造任务。但在客户端将生成器对象传递给主管类制造方法时还有另一种方式。在这种情况下,你在使用主管类生产产品时每次都可以使用不同的生成器。

五、代码实例

分步制造汽车
在本例中, 生成器模式允许你分步骤地制造不同型号的汽车。
示例还展示了生成器如何使用相同的生产过程制造不同类型的产品 (汽车手册)。
主管控制着构造顺序。 它知道制造各种汽车型号需要调用的生产步骤。 它仅与汽车的通用接口进行交互。 这样就能将不同类型的生成器传递给主管了。
最终结果将从生成器对象中获得, 因为主管不知道最终产品的类型。 只有生成器对象知道自己生成的产品是什么。

  1. 生成器
public interface Builder {
    void setCarType(CarType type);
    void setSeats(int seats);
    void setEngine(Engine engine);
    void setTransmission(Transmission transmission);
    void setTripComputer(TripComputer tripComputer);
    void setGPSNavigator(GPSNavigator gpsNavigator);
}
  1. 具体生成器
public class CarBuilder implements  Builder{

    private CarType type;
    private int seats;
    private Engine engine;
    private Transmission transmission;
    private TripComputer tripComputer;
    private GPSNavigator gpsNavigator;

    public void setCarType(CarType type) {
        this.type = type;
    }

    @Override
    public void setSeats(int seats) {
        this.seats = seats;
    }

    @Override
    public void setEngine(Engine engine) {
        this.engine = engine;
    }

    @Override
    public void setTransmission(Transmission transmission) {
        this.transmission = transmission;
    }

    @Override
    public void setTripComputer(TripComputer tripComputer) {
        this.tripComputer = tripComputer;
    }

    @Override
    public void setGPSNavigator(GPSNavigator gpsNavigator) {
        this.gpsNavigator = gpsNavigator;
    }

    public Car getResult() {
        return new Car(type, seats, engine, transmission, tripComputer, gpsNavigator);
    }
}

public class CarManualBuilder implements Builder{
    private CarType type;
    private int seats;
    private Engine engine;
    private Transmission transmission;
    private TripComputer tripComputer;
    private GPSNavigator gpsNavigator;

    @Override
    public void setCarType(CarType type) {
        this.type = type;
    }

    @Override
    public void setSeats(int seats) {
        this.seats = seats;
    }

    @Override
    public void setEngine(Engine engine) {
        this.engine = engine;
    }

    @Override
    public void setTransmission(Transmission transmission) {
        this.transmission = transmission;
    }

    @Override
    public void setTripComputer(TripComputer tripComputer) {
        this.tripComputer = tripComputer;
    }

    @Override
    public void setGPSNavigator(GPSNavigator gpsNavigator) {
        this.gpsNavigator = gpsNavigator;
    }

    public Manual getResult() {
        return new Manual(type, seats, engine, transmission, tripComputer, gpsNavigator);
    }
}

  1. 产品
public class Car {
    private final CarType carType;
    private final int seats;
    private final Engine engine;
    private final Transmission transmission;
    private final TripComputer tripComputer;
    private final GPSNavigator gpsNavigator;
    private double fuel = 0;

    public Car(CarType carType, int seats, Engine engine, Transmission transmission,
               TripComputer tripComputer, GPSNavigator gpsNavigator) {
        this.carType = carType;
        this.seats = seats;
        this.engine = engine;
        this.transmission = transmission;
        this.tripComputer = tripComputer;
        if (this.tripComputer != null) {
            this.tripComputer.setCar(this);
        }
        this.gpsNavigator = gpsNavigator;
    }

    public CarType getCarType() {
        return carType;
    }

    public double getFuel() {
        return fuel;
    }

    public void setFuel(double fuel) {
        this.fuel = fuel;
    }

    public int getSeats() {
        return seats;
    }

    public Engine getEngine() {
        return engine;
    }

    public Transmission getTransmission() {
        return transmission;
    }

    public TripComputer getTripComputer() {
        return tripComputer;
    }

    public GPSNavigator getGpsNavigator() {
        return gpsNavigator;
    }
}

public enum CarType {
    CITY_CAR, SPORTS_CAR, SUV
}

public class Engine {
    private final double volume;
    private double mileage;
    private boolean started;

    public Engine(double volume, double mileage) {
        this.volume = volume;
        this.mileage = mileage;
    }

    public void on() {
        started = true;
    }

    public void off() {
        started = false;
    }

    public boolean isStarted() {
        return started;
    }

    public void go(double mileage) {
        if (started) {
            this.mileage += mileage;
        } else {
            System.err.println("Cannot go(), you must start engine first!");
        }
    }

    public double getVolume() {
        return volume;
    }

    public double getMileage() {
        return mileage;
    }
}

public class GPSNavigator {
    private String route;

    public GPSNavigator() {
        this.route = "221b, Baker Street, London  to Scotland Yard, 8-10 Broadway, London";
    }

    public GPSNavigator(String manualRoute) {
        this.route = manualRoute;
    }

    public String getRoute() {
        return route;
    }
}

public enum Transmission {
    SINGLE_SPEED, MANUAL, AUTOMATIC, SEMI_AUTOMATIC
}

public class Manual {
    private final CarType carType;
    private final int seats;
    private final Engine engine;
    private final Transmission transmission;
    private final TripComputer tripComputer;
    private final GPSNavigator gpsNavigator;

    public Manual(CarType carType, int seats, Engine engine, Transmission transmission,
                  TripComputer tripComputer, GPSNavigator gpsNavigator) {
        this.carType = carType;
        this.seats = seats;
        this.engine = engine;
        this.transmission = transmission;
        this.tripComputer = tripComputer;
        this.gpsNavigator = gpsNavigator;
    }

    public String print() {
        String info = "";
        info += "Type of car: " + carType + "\n";
        info += "Count of seats: " + seats + "\n";
        info += "Engine: volume - " + engine.getVolume() + "; mileage - " + engine.getMileage() + "\n";
        info += "Transmission: " + transmission + "\n";
        if (this.tripComputer != null) {
            info += "Trip Computer: Functional" + "\n";
        } else {
            info += "Trip Computer: N/A" + "\n";
        }
        if (this.gpsNavigator != null) {
            info += "GPS Navigator: Functional" + "\n";
        } else {
            info += "GPS Navigator: N/A" + "\n";
        }
        return info;
    }
}

public class TripComputer {
    private Car car;

    public void setCar(Car car) {
        this.car = car;
    }

    public void showFuelLevel() {
        System.out.println("Fuel level: " + car.getFuel());
    }

    public void showStatus() {
        if (this.car.getEngine().isStarted()) {
            System.out.println("Car is started");
        } else {
            System.out.println("Car isn't started");
        }
    }
}

  1. 主管
public class Director {
    public void constructSportsCar(Builder builder) {
        builder.setCarType(CarType.SPORTS_CAR);
        builder.setSeats(2);
        builder.setEngine(new Engine(3.0, 0));
        builder.setTransmission(Transmission.SEMI_AUTOMATIC);
        builder.setTripComputer(new TripComputer());
        builder.setGPSNavigator(new GPSNavigator());
    }

    public void constructCityCar(Builder builder) {
        builder.setCarType(CarType.CITY_CAR);
        builder.setSeats(2);
        builder.setEngine(new Engine(1.2, 0));
        builder.setTransmission(Transmission.AUTOMATIC);
        builder.setTripComputer(new TripComputer());
        builder.setGPSNavigator(new GPSNavigator());
    }

    public void constructSUV(Builder builder) {
        builder.setCarType(CarType.SUV);
        builder.setSeats(4);
        builder.setEngine(new Engine(2.5, 0));
        builder.setTransmission(Transmission.MANUAL);
        builder.setGPSNavigator(new GPSNavigator());
    }
}

  1. 测试类
public class Demo {
    public static void main(String[] args) {
        Director director = new Director();

        CarBuilder builder = new CarBuilder();
        director.constructSportsCar(builder);
        Car car = builder.getResult();
        System.out.println("Car built:\n" + car.getCarType());


        CarManualBuilder manualBuilder = new CarManualBuilder();
        director.constructSportsCar(manualBuilder);
        Manual carManual = manualBuilder.getResult();
        System.out.println("\nCar manual built:\n" + carManual.print());
    }
}

  1. 结果
    在这里插入图片描述

六、优缺点

  1. 你可以分步创建对象,暂缓创建步骤或递归运行创建步骤。
  2. 生成不同形式的产品时,你可以复用相同的制造代码。
  3. 单一职责原则。你可以将复杂构造代码从产品的业务逻辑中分离出来。
  4. 由于该模式需要新增多个类,因此代码整体复杂程度会有所增加

七、与其他模式的关系

  • 在许多设计工作的初期都会使用工厂方法(较为简单,而且可以更方便地通过子类进行定制),随后演化为使用抽象工厂、原型或生成器(更灵活但更加复杂)。

  • 生成器重点关注如何分步生成复杂对象。抽象工厂专门用于生产一系列相关对象。抽象工厂会马上返回产品,生成器则允许你在获取产品前执行一些额外构造步骤。

  • 你可以在创建复杂组合树时使用生成器,因为这可使其构造步骤以递归的方式运行。

  • 你可以结合使用生成器和桥接模式: 主管类负责抽象工作,各种不同的生成器负责实现工作。

  • 抽象工厂、生成器和原型都可以用单例来实现。


总结

  1. 清晰地定义通用步骤, 确保它们可以制造所有形式的产品。
    否则你将无法进一步实施该模式。

  2. 在基本生成器接口中声明这些步骤。

  3. 为每个形式的产品创建具体生成器类,并实现其构造步骤。不要忘记实现获取构造结果对象的方法。你不能在生成器接口中声明该方法,因为不同生成器构造的产品可能没有公共接口,因此你就不知道该方法返回的对象类型。但是,如果所有产品都位于单一类层次中,你就可以安全地在基本接口中添加获取生成对象的方法。

  4. 考虑创建主管类。它可以使用同一生成器对象来封装多种构造产品的方式。

  5. 客户端代码会同时创建生成器和主管对象。构造开始前,客户端必须将生成器对象传递给主管对象。通常情况下,客户端只需调用主管类构造函数一次即可。主管类使用生成器对象完成后续所有制造任务。还有另一种方式,那就是客户端可以将生成器对象直接传递给主管类的制造方法。

  6. 只有在所有产品都遵循相同接口的情况下,构造结果可以直接通过主管类获取。否则,客户端应当通过生成器获取构造结果。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
Factorytalk view 简明培训 ------深圳库马克 杨小贤 ME 工程设计简记: 1、 新建工程 "文件"菜单中选择"新建应用程序" ,弹出如下对话框: 输入应用程序名称,语言选择英语(en-US) ,然后单击创建完成新建程序,如果想要导入 其他的工程,可以单击"导入"按钮,选择需要导入的程序,按"完成"导入已有工程。 (注 意:新建的工程与导入的工程语言要一直,否则出现乱码) 。 2、 工程设置与起动运行设置 在工程浏览器窗口中的工程目录下的系统目录中, 可以对工程运行、 启动、 安全等进行设置。 在工程窗口大小中根据触摸屏型号即尺寸分辨率等选择适当的大小。在"运行时"设置页面 里设置工程运行时是否有标题栏、边框以及工程窗口位置。 在"启动"设置中,可以设置在工程运行时是否启动"报警" 、 "参考信息"等,在初始画面 中可以设置工程启动运行时的初始画面。 3、 创建通讯 在工程浏览器窗口中,点击进入 RSLinx Enterprise(此项为可选安装选项,在安装软件时必 须安装 RSLinx Enterprise 才能显示此选项) , 双击 Communication Setup 进入通讯设置, 如下 图所示: 点击添加,并输入通讯快捷方式名称(自定) ,在"设计"(本地)和"运行时"(目标)选项 卡中分别设置本地通讯和运行时通讯模式。 本地工作站上的设置, 用于构建及测试应用程序 ("通信设置"编辑器, "设计"(本地) 选项卡。 ) 部署到实际目标设备 (例如 PanelView Plus) 的设置("通信设置"编辑器"运行时"(目标)选项卡。 ) 根据联网设备的型号,通讯模式等在"设计"和"本地"选项卡中添加相应的设备驱动和设 备。 4、 创建 HMI 标签 在工程浏览器中,进入 HMI 标签,双击标签进入如下图所示界面: 新建标签, 输入标签名称, 类型, 描述等, 在数据源中选择设备类型为 "设备" 还是 "内存" , 如果设备类型为设备,则需要输入地址,地址格式为设备名称加实际设备内存地址。也可以 通过数据库浏览器直接导入 PLC 变量。 5、 创建画面显示 在"文件"菜单中,选择"新建">"显示"。或在浏览器的"图形"文件中,右键单击显示图标, 选择"新建"。创建新的画面显示。在"编辑"菜单中,选择显示设置 ,如下图所示: 可以设置画面的显示类型、安全码、背景色和最大标签更新速率(画面数据刷新时间,默认 1 秒) 。 详细画面过程见帮助文件 6、 创建报警 在"报警设置"编辑器中指定要监视的标签或表达式, 触发报警、 报警设置和弹出报警显示 (如 果存在)的条件以及当报警发生时要执行的操作。 注意:要在运行时监视报警,必须在"启动"编辑器中选中"报警"复选框。 在"浏览器"窗口,打开 Alarms 文件夹。 双击"报警设置"图标打开编辑器。 在"触发器"、"信息"和"高级"选项卡中设置报警。 单击"确定"保存变更,关闭编辑器。 向选择触发器列表框里添加触发报警的变量,选择触发器类型和触发器标记。 在信息对话框中添加触发器以及报警信息,设置好触发器值,创建报警成功。 7、 设置运行时安全 在工程浏览器窗口,双击运行时安全,添加帐户,设置权限以及安全码。 8、 测试运行 工程编辑完成后,可通过"应用程序"---》 "测试应用程序"命令来模拟测试显示及功能等。 9、 生成运行时文件 确定工程编辑无误后,通过"应用程序"-----》 "创建运行时应用程序" ,生成触摸屏上能运 行的工程文件。 10、 将运行文件下载到触摸屏 从开发系统的"开始"菜单中,依次选择"Rockwell Software > FactoryTalk View > 工 具 > ME 传输工具"。 或 从 FactoryTalk View Studio 中,从"工具"菜单选择"传输工具", 或 从 FactoryTalk View Studio 中,在工具栏上单击"传输工具"图标。 "传输工具"对话框打开。如下图所示: 在源程序中选择要下载的到触摸屏的程序, 选择程序存储位置, 点选 "启动时运行应用程序" , 点选"替换通信"以替换当前通信模式。在目标终端列表框中选择要下载程序的目标设备, 点击下载,下载完成后,触摸屏自动重启并进入运行画面。 对于首次使用的触摸屏, 一定要向触摸屏下载中文字体, 否则中文有可能不显示或者显示乱 码。 SE 工程设计简记: 1、 创建新项目 启动 FactoryTalk View Studio。 选择 site Edition(本地)————》继续 选择新建,输入应用程序名称,语言选择英语,单击创建,新建一个新项目。 2、 按下图所示添加 RSLinx Enterprise 服务器,连接 PLC。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值