设计模式之工厂模式

工厂方法模式定义了创建对象的接口,但由子类决定要实例化的类是哪一个。工厂方法让类把实例化推迟到子类。

来解析这个定义:

  1. 创建对象”这个点非常关键,表明了工厂模式的职责就是为了创建对象的,而不是干其他委托、代理什么的。同时也将创建对象这个职责从Client迁移出去了。
  2. 子类决定要实例化的类是哪一个”,说明创建对象的方法被子类继承,而这个方法的返回值是目标类,并且隐含着一个更重要的点,父类中定义的这个方法的返回值一定是一个目标抽象类,而子类返回是目标具体类。
  3. 实例化推迟到子类”,表明是在客户端调用时,通过选用不同的子类,来决定目标对象的具体类。

学习设计模式,一个关键的自我校验,是能够自己给自己出题,构建场景,并通过目标设计模式来解决。

💡
假设我们有一个汽车制造系统,系统需要生产不同类型的汽车(如电动车、燃油车)。我们可以使用工厂方法模式来实现这个系统,使得不同类型的汽车由不同的工厂子类来创建。
// 1. 定义抽象产品类(汽车)
abstract class Car {
    public abstract void drive();
}

// 2. 定义具体产品类(电动车和燃油车)
class ElectricCar extends Car {
    @Override
    public void drive() {
        System.out.println("Driving an Electric Car!");
    }
}

class GasolineCar extends Car {
    @Override
    public void drive() {
        System.out.println("Driving a Gasoline Car!");
    }
}

// 3. 定义抽象创建者类(汽车工厂)
abstract class CarFactory {
    // 工厂方法
    public abstract Car createCar();

    // 交付汽车的方法
    public void deliverCar() {
        Car car = createCar();
        System.out.print("Factory: Delivering ");
        car.drive();
    }
}

// 4. 定义具体创建者类(电动车工厂和燃油车工厂)
class ElectricCarFactory extends CarFactory {
    @Override
    public Car createCar() {
        return new ElectricCar();
    }
}

class GasolineCarFactory extends CarFactory {
    @Override
    public Car createCar() {
        return new GasolineCar();
    }
}

// 5. 客户端代码
public class FactoryMethodExample {
    public static void main(String[] args) {
        System.out.println("Client: Ordering an Electric Car");
        CarFactory electricFactory = new ElectricCarFactory();
        electricFactory.deliverCar();

        System.out.println("\nClient: Ordering a Gasoline Car");
        CarFactory gasolineFactory = new GasolineCarFactory();
        gasolineFactory.deliverCar();
    }
}

这是一个简单工厂的实现,将Car具体类的创建职责从Client中剥离出来,放到CarFactory中。

考虑一个新需求,当需要新增加一个混动类型的车型时,首先需要增加Car的的子类HybridCar,这个动作属于扩展,不需要修改其他类,符合开闭原则,接着需要CarFactory支持混动车型的制造,需要修改CarFactory的代码,不符合开闭原则。发现没有,对新需求支持时进行横向扩展,而不用修改旧的代码逻辑,是每个设计模式都要考虑的事情,更直白简单的描述,设计模式就是在不同的角度来支持新需求能够扩展而非修改。所谓不同角度,包括职责的扩展、新对象生成的扩展、订阅者的扩展等等。

如何解决,常规套路了,利用扩展子类来替代修改原有类,将CarFactory这个具体类向上抽离出超类。如此改造后,就演化出工厂方法模式了。

// 1. 定义抽象产品类(汽车)
abstract class Car {
    public abstract void drive();
}

// 2. 定义具体产品类(电动车和燃油车)
class ElectricCar extends Car {
    @Override
    public void drive() {
        System.out.println("Driving an Electric Car!");
    }
}

class GasolineCar extends Car {
    @Override
    public void drive() {
        System.out.println("Driving a Gasoline Car!");
    }
}

// 3. 定义抽象创建者类(汽车工厂)
abstract class CarFactory {
    // 工厂方法
    public abstract Car createCar();

    // 交付汽车的方法
    public void deliverCar() {
        Car car = createCar();
        System.out.print("Factory: Delivering ");
        car.drive();
    }
}

// 4. 定义具体创建者类(电动车工厂和燃油车工厂)
class ElectricCarFactory extends CarFactory {
    @Override
    public Car createCar() {
        return new ElectricCar();
    }
}

class GasolineCarFactory extends CarFactory {
    @Override
    public Car createCar() {
        return new GasolineCar();
    }
}

// 5. 客户端代码
public class Client {
    public static void main(String[] args) {
        System.out.println("Client: Ordering an Electric Car");
        CarFactory electricFactory = new ElectricCarFactory();
        electricFactory.deliverCar();

        System.out.println("\nClient: Ordering a Gasoline Car");
        CarFactory gasolineFactory = new GasolineCarFactory();
        gasolineFactory.deliverCar();
    }
}

再次强调,在类图中可以看到,我们利用的抽象类的继承,而不是接口的实现,这两者的主要区别在于子类是否有共享的逻辑,如果有则选择抽象继承,如果没有则选择接口实现,并不影响模式本身的结构。

需求再变,汽车工厂除了生产汽车外,还要生产相配套的产品,如轮胎,要如何实现。燃油车工厂生产燃油车和燃油车轮胎,电车工厂生产电车和电车轮胎,车和轮胎是一个产品族。而“产品族”正是抽象工厂的模式意图,虽然类图上看着与工厂方法很相似,实现上也是自然的衍生,主要的区别是设计意图,当抽象工厂模式只涉及一个产品时,它的代码结构和工厂方法模式几乎一样。

// 1. 抽象产品:汽车
abstract class Car {
    public abstract void drive();
}

// 2. 具体产品:电动车
class ElectricCar extends Car {
    @Override
    public void drive() {
        System.out.println("Driving an Electric Car!");
    }
}

// 3. 具体产品:燃油车
class GasolineCar extends Car {
    @Override
    public void drive() {
        System.out.println("Driving a Gasoline Car!");
    }
}

// 4. 抽象产品:轮胎
abstract class Tire {
    public abstract void inflate();
}

// 5. 具体产品:电动车轮胎
class ElectricCarTire extends Tire {
    @Override
    public void inflate() {
        System.out.println("Inflating Electric Car Tire!");
    }
}

// 6. 具体产品:燃油车轮胎
class GasolineCarTire extends Tire {
    @Override
    public void inflate() {
        System.out.println("Inflating Gasoline Car Tire!");
    }
}

// 7. 抽象工厂
abstract class AbstractCarFactory {
    // 创建汽车
    public abstract Car createCar();

    // 创建轮胎
    public abstract Tire createTire();
}

// 8. 具体工厂:电动车工厂
class ElectricCarFactory extends AbstractCarFactory {
    @Override
    public Car createCar() {
        return new ElectricCar();
    }

    @Override
    public Tire createTire() {
        return new ElectricCarTire();
    }
}

// 9. 具体工厂:燃油车工厂
class GasolineCarFactory extends AbstractCarFactory {
    @Override
    public Car createCar() {
        return new GasolineCar();
    }

    @Override
    public Tire createTire() {
        return new GasolineCarTire();
    }
}

// 10. 客户端代码
public class AbstractFactoryExample {
    public static void main(String[] args) {
        // 创建电动车工厂
        AbstractCarFactory electricFactory = new ElectricCarFactory();
        Car electricCar = electricFactory.createCar();
        Tire electricTire = electricFactory.createTire();

        System.out.println("Client: Ordering an Electric Car");
        electricCar.drive();
        electricTire.inflate();

        // 创建燃油车工厂
        AbstractCarFactory gasolineFactory = new GasolineCarFactory();
        Car gasolineCar = gasolineFactory.createCar();
        Tire gasolineTire = gasolineFactory.createTire();

        System.out.println("\nClient: Ordering a Gasoline Car");
        gasolineCar.drive();
        gasolineTire.inflate();
    }
}
  • 工厂方法模式
    • 用于创建单个对象。
    • 每个工厂子类只负责创建一种产品。
  • 抽象工厂模式
    • 用于创建一组相关对象(产品族)。
    • 每个工厂子类负责创建一组相关的产品。

可以看到,从简单工厂到工厂方法再到抽象工厂,都是随着需求而演变的,模式本身是为了更好的解决问题,因此在设计模式选择时,不要死搬硬套,而要与实际场景相结合,任何选择都是有利有弊的,取决于当前哪个考核点是最高优先级。

工厂模式,哪个点最重要,那就是职责 —— “对象创建”。

Read more

痛风带来的思考

昨晚一罐冰啤酒下去,睡觉时就感觉脚踝隐隐发作,果然早上起床直接下不来地。跟崴脚的感觉十分相似,无法行走,只能坐在一起上滑动,公司上班也去不了了,呆呆得躺在家里,下午疼痛感加剧,整个心思都在左脚的疼痛上,没有其他任何多余的精力去关注其他事情,而此刻的阳台,乃最美人间四月天,春日的微风吹拂着阳台的花儿,温暖的阳光抛洒下来,一切都如此惬意,而我却无心欣赏。 人在健康时,生活中有好多问题,但人在不健康时,生活中只剩一个问题。 我对这句话的理解更深刻了。人是健忘的,在疫情期间、在手术期间,这种感悟其实已经很深刻了,但是病情好转之后,人还是会被日常的琐碎、工作的烦扰搅乱心绪,没有专注的去享受生活本身的美好。 幸福的秘密在于,去享受我们所拥有的,而不是顽固的去追求所没有拥有的。阳光、草木、微风,都是幸福的玩意儿,应尽情的享受。 再等两天,脚完全恢复好了,身体健健康康后,我要以更轻盈的姿态去生活,不纠结他人的看法,不执着别人的认可,关注自己的能力,享受拥有的生后。 还有一个反省,针对咖啡、酒、烟,

By 李浩

设计模式之命令模式

命令模式将“请求”封装成对象,以便使用不同的请求、队列或者日志来参数化其他对象,命令模式也支持可撤销的操作。 来解析这个定义: 1. “将一个请求封装为一个对象”,请求原本是一个方法,现在要封装成一个对象,说明要新增类来完成。 2. “可以用不同的请求对客户进行参数化”,说明是将命令对象作为参数进行传递。 3. “队列”说明需要维护命令多个命令的列表队列。 4. “撤销”说明有命令对象有undo撤销方法。 命令模式在设计模式中,算是一个比较不好理解的模式,很重要的原因是不清楚设计意图,不清楚不用这个模式前有何问题,这个模式带了哪些好处,能解决什么问题。 上一篇状态模式中,看到了状态模式抽离的是状态(属性),向上提成状态对象。有了这个基础,再来理解命令模式就相对简单了。命令模式抽离的是行为(方法),向上提成命令对象。 两者都通过“对象化”来解耦和扩展系统,但解决的问题不同: * 状态模式:处理对象内部状态驱动的行为变化。 * 命令模式:处理行为请求的封装与调度。 💡智能家居遥控器,假设我们有一个智能家居遥控器,可以控制 灯(Light) 和

By 李浩

设计模式之状态模式

状态模式允许对象在内部状态改变时改变它的行为,对象看起来好像修改了它的类。 来解析这个定义: 1. “内部状态”表明对象内部有一个属性来表示状态。 2. “内部状态改变时改变它的行为,对象看起来好像修改了它的类”说明状态改变后对象的行为发生了非常大的变化,不像是同一类的行为。 从目前的分析中似乎无法推导出状态模式的类图结构。 从实际的例子出来,来看看状态模式是如何演进而来。 💡我们有一个文档审批系统,文档有以下状态和转换: 1. 草稿(Draft) → 提交 → 待审批(PendingReview) 2. 待审批 → 批准 → 已发布(Published) 3. 待审批 → 拒绝 → 草稿 4. 已发布 → 撤回 → 草稿 从直觉出发,会使用条件语句实现需求逻辑。 public class Document { private String state = "DRAFT"; // 初始状态为草稿 public void submit(

By 李浩

设计模式之代理模式

代理模式为另一个对象提供一个替身或占位符以控制对这个对象的访问。 解析这个定义: 1. “替身”表明在客户端看来,代理类与被代理类是同一类别,对客户端来说看上去没什么区别,依然能够满足诉求。如此可以看出代理类与被代理类来自同一个超类。 2. “控制对这个对象的访问”,能够控制访问,说明前提是能够访问,才能在访问之前做这个限制,即代理持有对真实对象的引用(或能创建它)。 当然可能会质疑,用继承的方式不是也能完成目标吗,用代理类去继承被代理类,然后重写方法,加入控制逻辑。但这违背了"组合优于继承"原则,代理类与被代理类强耦合。 💡我们要开发一个图片查看器,需求如下: 1. 图片加载开销大(从磁盘或网络加载耗时),希望首次显示时才加载(延迟加载)。 2. 某些图片需要权限校验,只有授权用户才能查看。 3. 客户端代码应统一接口,无需关心是直接加载图片还是通过代理。 // 1. 抽象接口(Subject) interface Image { void display(); } // 2. 真实对象(RealSubject)

By 李浩