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();
}
}
🌟 相当于这些类当作单个组件来使用
在软件设计中,组合和继承都是重要的构建模块的方式,但各自的使用场景有所不同。以下是一些考虑在何种情况下使用组合(即使用构造)而不是继承(即使用静态工厂方法)来创建对象的情况:
-
变化频繁的需求
通过组合对象,您可以在运行时或程序的不同部分根据需要替换和重用对象。 -
避免复杂的继承层次
如果系统中的类层次过于复杂,使用组合可以避免出现过多子类。 -
提升代码的可重用性
通过组合,您可以将不同的功能封装在独立的类中,然后在需要的地方进行组合,增强代码的可重用性。 -
实现多个行为
如果一个类需要实现多个相互独立的行为,使用组合可以使您将这些行为封装在不同的类中,而不是将所有代码都放在一个基类中。 -
动态组合能力
如果需要在运行时动态构建对象的行为,组合是更好的选择。您可以在运行时选择不同的行为组合而不需要重新构建类层次。 -
简化测试
组合通常使得单元测试更加容易,因为每个组成部分可以单独测试,而无需创建复杂的依赖关系。
举例
- 假设您正在构建一个游戏系统,需要创建不同类型的角色。
- 您可以使用组合来定义角色的属性,如生命值、攻击力等,并将这些属性封装在不同的组件类中。
- 您可以在🌟不同角色之间共享组件,或者通过不同的组件组合来创建新的角色类型,而不必使用复杂的继承图。
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");
}
}
(后续有遇到问题再添加)
声明:如本内容中存在错误或不准确之处,欢迎指正。转载时请注明原作者信息(麻辣香蝈蝈)。