《Effective Java:静态工厂方法的深度剖析》

Effective Java系列文章目录

补充内容 Windows通过SSH连接Linux
第一章 Linux基本命令的学习与Linux历史


一、前言

对《Effective Java》每条规定总结

  • 第一条:用静态工厂方法替代构造器

二、学习内容:

大部分知识点都了解只记录星星的重点

静态工厂方法的优点:

  • 有名称易阅读
  • 不必每次新建对象

🌟 可以返回原返回类型的任何子类型对象

  • 返回对象可以因为参数不同而不同

🌟 方法返回的对象所属的类,在编写包含该静态工厂方法的类时可以不存在。


三、问题描述

静态工厂方法的缺点:

  • 静态工厂方法通常🌟隐藏类的构造器,使得子类无法直接继承这些类。
  • 静态工厂方法程序员很难发现它们。与构造器不同,静态工厂方法在API文档中并不像构造器那样明确标识

鼓励组合而非继承

  • 尽管这个缺点看似不利,但它实际上鼓励程序员🌟使用组合(而非继承)。在设计不可变类型时,组合更灵活,更易于维护。

四、解决方案

4.1 可以返回原返回类型的任何子类型对象

4.1.1 示例代码

之后的讲解都在这段代码基础上续写

// 抽象形状类  
abstract class Shape {  
    public abstract void draw();  
}  

// 圆形类  
class Circle extends Shape {  
    @Override  
    public void draw() {  
        System.out.println("Drawing a Circle");  
    }  
}  

// 正方形类  
class Square extends Shape {  
    @Override  
    public void draw() {  
        System.out.println("Drawing a Square");  
    }  
}  

4.1.2 观察下面参考代码区别

ShapeFactory部分返回接口实现类

  • 构造方法
// 使用构造器创建形状(区别在于这里的构造器不易返回子类)  
class ShapeFactory {  
    public Shape createCircle() {  
        return new Circle();  
    }  

    public Shape createSquare() {  
        return new Square();  
    }  
}  

// 使用案例  
public class Main {  
    public static void main(String[] args) {  
        ShapeFactory factory = new ShapeFactory();  
        Shape circle = factory.createCircle();  
        Shape square = factory.createSquare();  
        
        circle.draw();  
        square.draw();  
    }  
}
  • 静态工厂方法

// 使用静态工厂方法创建形状  
class ShapeFactory {  
    public static Shape getShape(String type) {  
        if ("Circle".equalsIgnoreCase(type)) {  
            return new Circle();  
        } else if ("Square".equalsIgnoreCase(type)) {  
            return new Square();  
        }  
        return null;  
    }  
}  

// 使用案例  
public class Main {  
    public static void main(String[] args) {  
        Shape circle = ShapeFactory.getShape("Circle");  
        Shape square = ShapeFactory.getShape("Square");  
        
        circle.draw();  
        square.draw();  
    }  
}

我们对比一下

  • 不易返回体现在用构造的话每次新建形状的话要多增加一个方法

🌟同时注意这些类都是形状的子类
在这里插入图片描述

4.2 鼓励组合而非继承

4.2.1 组合的优势

灵活性:

  • 可以轻松替换或修改Car中的组件,如更换引擎或轮胎,而🌟不需要受到继承关系的限制

避免复杂的继承层次:

  • 🌟组合使得类的设计更加直观

代码重用:

  • 组合可以重用相同的组件,不同的对象可以拥有不同的组合,而不必创建多个子类。
  • 例如,Truck类也可以结合Engine和Tire类,使用不同的参数。

更易于维护:

  • 如果需要修改某个特性的行为,只需修改相应的类,而无需修改依赖于该特性的多个子类。

4.2.2 组合的场景

参考下面代码理解:

// 引擎类  
// 轮胎类  
// 传动系统类  
// 这些不重要只是举例

// 车类,使用组合  
class Car {  
    private Engine engine;  
    private Tire tire;  
    private Transmission transmission;  

    public Car(Engine engine, Tire tire, Transmission transmission) {  
        this.engine = engine;  
        this.tire = tire;  
        this.transmission = transmission;  
    }  

    public void displayDetails() {  
        System.out.println("Engine type: " + engine.getType());  
        System.out.println("Tire size: " + tire.getSize());  
        System.out.println("Transmission type: " + transmission.getType());  
    }  
}  

// 使用案例  
public class Main {  
    public static void main(String[] args) {  
        Engine engine = new Engine("V8");  
        Tire tire = new Tire(17);  
        Transmission transmission = new Transmission("Automatic");  

        Car car = new Car(engine, tire, transmission);  
        car.displayDetails();  
    }  
}

🌟 相当于这些类当作单个组件来使用

在这里插入图片描述

在软件设计中,组合和继承都是重要的构建模块的方式,但各自的使用场景有所不同。以下是一些考虑在何种情况下使用组合(即使用构造)而不是继承(即使用静态工厂方法)来创建对象的情况:

  1. 变化频繁的需求
    通过组合对象,您可以在运行时或程序的不同部分根据需要替换和重用对象。

  2. 避免复杂的继承层次
    如果系统中的类层次过于复杂,使用组合可以避免出现过多子类。

  3. 提升代码的可重用性
    通过组合,您可以将不同的功能封装在独立的类中,然后在需要的地方进行组合,增强代码的可重用性。

  4. 实现多个行为
    如果一个类需要实现多个相互独立的行为,使用组合可以使您将这些行为封装在不同的类中,而不是将所有代码都放在一个基类中。

  5. 动态组合能力
    如果需要在运行时动态构建对象的行为,组合是更好的选择。您可以在运行时选择不同的行为组合而不需要重新构建类层次。

  6. 简化测试
    组合通常使得单元测试更加容易,因为每个组成部分可以单独测试,而无需创建复杂的依赖关系。

举例

  • 假设您正在构建一个游戏系统,需要创建不同类型的角色。
  • 您可以使用组合来定义角色的属性,如生命值、攻击力等,并将这些属性封装在不同的组件类中。
  • 您可以在🌟不同角色之间共享组件,或者通过不同的组件组合来创建新的角色类型,而不必使用复杂的继承图

4.3 静态工厂方法的返回类型在定义静态工厂方法的类时可以不存在

🌟静态工厂方法的返回类型在定义静态工厂方法的类时并不存在

// 抽象类  
public abstract class Shape {  
    public abstract void draw();  
}  

// 工厂类  
public class ShapeFactory {  
    public static Shape createShape(String type) {  
        if ("Circle".equalsIgnoreCase(type)) {  
            return new Circle(); // Circle 是一个具体的实现类  
        } else if ("Square".equalsIgnoreCase(type)) {  
            return new Square(); // Square 是另一个具体的实现类  
        }  
        return null;  
    }  
}  

// 这时候,如果我们还没有实现 Circle 和 Square 类,  
// 但只定义了 Shape 接口或抽象类,  
// 这就是一个示例,其中工厂类存在,而返回类型(Circle、Square)尚不存在。
  • 在主包中,我们创建一个工厂类ShapeFactory,使用静态工厂方法来创建Shape对象,但在这个类中并没有直接引用Circle或Square类
public class ShapeFactory {  
    public static Shape createShape(String type) {  
        if ("Circle".equalsIgnoreCase(type)) {  
            return new Circle();  
        } else if ("Square".equalsIgnoreCase(type)) {  
            return new Square();  
        } else if ("Triangle".equalsIgnoreCase(type)) {  
            // 可能在未来实现  
            // return new Triangle();  
        }  
        return null;  
    }  
}  
// 这个时候,Triangle类可能还没有实现,但工厂方法已经具备了添加它的逻辑。
  • 返回的实现可在不同地方定义
// 在图形库模块(外部包)中  
public class Circle implements Shape {  
    public void draw() {  
        System.out.println("Drawing a Circle");  
    }  
}  

// 在图形工厂模块(内部包)中  
public class ShapeFactory {  
    public static Shape createShape(String type) {  
        if ("Circle".equalsIgnoreCase(type)) {  
            return new Circle(); // 这里的 Circle 可能在不同的模块中定义  
        }  
        return null;  
    }  
}

4.4 隐藏的构造器

4.4.1 单例模式

  • 创建实例的控制:通过静态工厂方法,类可以实现单例模式,控制实例返回。

不解释太多就是构造修饰符是private

public class Singleton {  
    private static final Singleton INSTANCE = new Singleton();  

    // 私有构造器,防止外部实例化  
    private Singleton() {}  

    // 静态工厂方法返回单一实例  
    public static Singleton getInstance() {  
        return INSTANCE;  
    }  
}  
// 使用案例  
public class Main {  
    public static void main(String[] args) {  
        Singleton instance1 = Singleton.getInstance();  
        Singleton instance2 = Singleton.getInstance();  
        
        System.out.println(instance1 == instance2); // 输出: true  
    }  
}

4.4.2 静态方法无法被重写

  • 不构成多态性:静态方法无法被重写,因此无法实现动态绑定。
class Base {  
    public static void display() {  
        System.out.println("Base display");  
    }  
}  

class Derived extends Base {  
    public static void display() {  
        System.out.println("Derived display");  
    }  
}  

// 使用案例  
public class Main {  
    public static void main(String[] args) {  
        Base b = new Derived();  
        b.display(); // 输出: Base display  
    }  
}

4.4.3 无法继承

  • 限制扩展能力:隐藏构造器导致无法通过继承来扩展功能,限制了灵活性。
class Parent {  
    private Parent() {  
        System.out.println("Parent Constructor");  
    }  
}  

class Child extends Parent {  
    // 无法调用 Parent 的构造器  
    public Child() {  
        // super(); // 编译错误,无法访问 Parent 的构造器  
        System.out.println("Child Constructor");  
    }  
}  

// 使用案例  
public class Main {  
    public static void main(String[] args) {  
        // Parent parent = new Parent(); // 编译错误,无法直接实例化  
        // Child child = new Child(); // 编译错误,无法继承未公开的构造器  
    }  
}

五、总结:

5.1 学习总结

5.1.1 静态工厂

简而言之,静态工厂方法和公有构造器都各有用处,我们需要理解它们各自的长处。

  • 静态工厂经常更加合适,因此切忌第一反应就是提供公有的构造器,而不先考虑静态工厂。
    (接口/抽象)类接收 ShapeFactory 返回的(接口/抽象)子类
// Shape.java - 定义形状的接口  
// Circle.java - 实现 Shape 接口  
// Square.java - 实现 Shape 接口  
package shapes;  

public class Square implements Shape {  
    public void draw() {  
        System.out.println("Drawing a Square");  
    }  
}  

// ShapeFactory.java - 不直接引用具体实现  
package main;  

import shapes.Shape; // 引入接口,不引入具体类  

public class ShapeFactory {  
    public static Shape createShape(String type) {  
        switch (type.toLowerCase()) {  
            case "circle":  
                return new Circle();  
            case "square":  
                return new Square();  
            default:  
                throw new IllegalArgumentException("Unknown shape type");  
        }  
    }  
}

🌟 灵活性和可扩展性:当你想要增加更多的形状(例如,Triangle 和 Rectangle),只需要实现 Shape 接口,并在工厂方法中添加相应的逻辑,而不需要修改任何其他地方的代码。
🌟 新技术的引入:如果未来需要更改具体实现(例如,使用不同的图形库),只需修改工厂类的实现即可,不会影响到使用这个工厂的现有代码。
🌟静态工厂方法的返回类型在定义静态工厂方法的类时并不存在

Shape shape = ShapeFactory.createShape("Circle");  
shape.draw(); // 客户端只关心Shape的接口,而不知道shape的具体类型

使用构造器的情况:

  • 简单:🌟对象创建过程比较简单。
  • 直观:直接创建对象,易于理解。

使用静态工厂方法的情况:

  • 命名清晰:可以用有意义的名字,增强可读性。
  • 重用实例:可以返回已存在的实例(比如单例)。
  • 灵活返回:🌟可以返回不同子类的实例
  • 隐藏细节:简化接口,不暴露内部实现

5.1.2 注册机制

这个只是练手不是书中的内容仅做参考

  • 可以维护一个 Map<String, Supplier>,其中 Supplier
    是一个函数式接口,它可以返回一个 Shape 实例。这种方法可以让你在不修改工厂代码的情况下注册新形状。
import java.util.HashMap;  
import java.util.Map;  
import java.util.function.Supplier;  

public class ShapeFactory {  
    private static final Map<String, Supplier<Shape>> shapeMap = new HashMap<>();  

    static {  
        shapeMap.put("circle", Circle::new);  
        shapeMap.put("square", Square::new);  
        // 可以在这里注册更多形状  
    }  

    public static Shape createShape(String type) {  
        Supplier<Shape> shapeSupplier = shapeMap.get(type.toLowerCase());  
        if (shapeSupplier != null) {  
            return shapeSupplier.get();  
        }  
        throw new IllegalArgumentException("Unknown shape type");  
    }  
}

(后续有遇到问题再添加)


声明:如本内容中存在错误或不准确之处,欢迎指正。转载时请注明原作者信息(麻辣香蝈蝈)。

在这里插入图片描述

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值