好代码与烂代码
对代码质量的评判不能依据笼统的感觉,而是根据精准的标准去判断
我们应该从以下角度去判断自己写的代码到底是不是屎山:
-
可维护性(Maintainability):能够以最小的成本和最快的速度修改或优化代码。可维护性高的代码能够降低修改引入错误的风险,并且降低了维护成本。
-
灵活性(Flexibility):原有代码不能排斥新功能,不会和扩展的代码发生冲突。简单的来说就是,代码必须要预留足够的扩展点,可以封装一些类,以供扩展功能使用。要能快速适应多种业务需求。
-
可读性(Readability):代码必须要符合编程规范,包括命名规范,语法规范,缩进规范等等。代码中不要出现过长的方法或者类。可读性高的代码能够使他人更容易理解代码的逻辑和实现细节,减少了维护成本。
-
简洁性(Simplicity):在能实现功能的前提下,优先考虑最简单的写法,而不是一味追求炫技。
-
可扩展性(Scalability):新加的功能对原有代码的影响程度最小。
-
可测试性(Testability):可测试的代码易于编写单元测试、集成测试和自动化测试,并且能够快速准确地发现错误。
通过Spring框架的示例,说明这些标准:
可维护性(Maintainability)
一个符合良好设计原则、遵循设计模式的Java应用,比如使用合适的面向对象设计、分层架构等,会提高代码的可维护性。Spring框架提供的依赖注入和面向切面编程等特性,也能够提高代码的可维护性。
灵活性(Flexibility)
使用接口、抽象类和设计模式等技术,能够提高代码的灵活性。在Spring框架中,利用依赖注入和面向切面编程等特性,可以实现代码的松耦合,从而提高灵活性。
可读性(Readability)
良好的命名规范、注释、代码风格以及逻辑清晰的代码结构,可以提高代码的可读性。在Spring框架中,合理利用注解,比如@Autowired
、@Component
等,能够增强代码的可读性。
简洁性(Simplicity)
遵循KISS(Keep It Simple, Stupid)原则,保持代码简洁易懂。Spring框架提供的依赖注入和AOP等功能,可以简化代码的复杂性,提高开发效率。
可扩展性(Scalability)
合理的分层架构、模块化设计以及设计模式的使用,可以提高代码的可扩展性。Spring框架提供的IOC容器和AOP等特性,能够方便地实现模块之间的解耦和功能扩展。
可测试性(Testability)
编写可测试的代码,包括使用依赖注入、编写单元测试、模块化设计等。Spring框架提供的IOC容器和AOP等特性,使得代码更易于测试,并且可以利用Spring Boot框架提供的测试支持,编写高效的单元测试和集成测试。
当谈到代码质量时,可复用性是另一个重要的标准,它指的是代码能否被轻松地重用在不同的上下文中,而不需要大量修改。
可复用性(Reusability)
在设计和实现代码时,考虑到代码的通用性和可重用性是至关重要的。
-
模块化设计(Modular Design):将代码划分为独立的模块,每个模块负责特定的功能或任务。这样的设计使得这些模块可以在不同的项目中被重复使用。
-
设计模式(Design Patterns):使用设计模式来解决常见的设计问题,例如工厂模式、单例模式、观察者模式等。这些设计模式提供了经过验证的解决方案,可以在不同的场景中被重用。
-
组件化开发(Component-Based Development):将代码封装成可重用的组件,这些组件可以在不同的应用中被调用和组合。例如,在Java中可以使用JavaBean或者自定义的Spring组件。
-
标准化接口(Standardized Interfaces):定义清晰的接口和API,使得代码可以通过这些接口进行交互。这样的设计使得不同实现可以轻松地替换,并且提高了代码的灵活性和可维护性。
-
库和框架(Libraries and Frameworks):使用现有的库和框架来实现通用功能,而不是从头开始编写代码。这样可以节省时间和精力,并且可以借助社区的力量来改进和维护这些库和框架。
好代码的原则
想要写出高质量,性能好的代码,就必须要遵守一些编程的思想
重点掌握设计原则和设计模式,最根本要解决的问题就是,让我们的代码能够很方便的扩展新功能
六大设计原则是面向对象编程中的基本指导原则,它们有助于编写可维护、灵活和可扩展的代码。
- 单一职责原则(Single Responsibility Principle,SRP):一个类应该只有一个引起变化的原因。换句话说,一个类应该只有一个责任。因此,拒绝编写大而全的类。
示例:假设有一个名为Car
的类,它负责管理汽车的行驶和维护。如果在同一个类中既负责行驶逻辑又负责维护逻辑,违反了单一职责原则。应该将这两个责任分别封装到两个不同的类中,比如DrivingService
和MaintenanceService
。
// 违反单一职责原则的代码
public class Car {
public void drive() {
// driving logic
}
public void maintain() {
// maintenance logic
}
}
- 开放-封闭原则(Open-Closed Principle,OCP):用抽象的方式规范代码结构,用具体实现的方式实现业务功能,并且抽象层是无法被修改
示例:假设有一个图形类Shape
,现在需要添加一个新的图形类型。按照开放-封闭原则,应该通过创建新的子类来扩展Shape
,而不是修改Shape
类本身。
// 违反开放-封闭原则的代码
public class Shape {
private String type;
public Shape(String type) {
this.type = type;
}
public void draw() {
if (type.equals("circle")) {
// draw circle
} else if (type.equals("square")) {
// draw square
}
}
}
- 里氏替换原则(Liskov Substitution Principle,LSP):就是指多态,即父类的方法可以通过多种不同的重写,被实现。此时,父类仅仅起到一个规范的作用。
示例:假设有一个Rectangle
类和一个Square
类,Square
是Rectangle
的子类。按照里氏替换原则,任何使用Rectangle
的地方都可以替换成Square
而不影响程序的正确性。
// 违反里氏替换原则的代码
public class Rectangle {
protected int width;
protected int height;
public void setWidth(int width) {
this.width = width;
}
public void setHeight(int height) {
this.height = height;
}
}
public class Square extends Rectangle {
@Override
public void setWidth(int width) {
this.width = width;
this.height = width;
}
@Override
public void setHeight(int height) {
this.width = height;
this.height = height;
}
}
- 依赖倒置原则(Dependency Inversion Principle,DIP):高层模块不应该依赖于低层模块,二者都应该依赖于抽象;抽象不应该依赖于具体实现细节,具体实现细节应该依赖于抽象。
其实,我们的常用的MVC架构就用到这个原则,比如,UserController(高层)依赖UserService接口(抽象层),而不是直接调UserServiceImpl(底层)
示例:假设有一个Driver
类,它直接依赖于Car
类。按照依赖倒置原则,应该通过引入接口或抽象类,让Driver
类依赖于抽象而非具体实现。
// 违反依赖倒置原则的代码
public class Driver {
private Car car;
public Driver(Car car) {
this.car = car;
}
public void driveCar() {
car.drive();
}
}
- 接口隔离原则(Interface Segregation Principle,ISP):客户端不应该依赖于它不使用的接口。换句话说,所设计的接口的影响范围越小越好,设计得越精准越好。
示例:假设有一个Worker
接口,其中包含了work
和eat
两个方法。但是有些工作者并不需要eat
方法。按照接口隔离原则,应该将Worker
接口拆分为多个接口,每个接口包含特定的方法。
// 违反接口隔离原则的代码
public interface Worker {
void work();
void eat();
}
public class Programmer implements Worker {
@Override
public void work() {
// coding logic
}
@Override
public void eat() {
// eating logic
}
}
- 合成复用原则(Composite Reuse Principle,CRP):尽量使用合成/聚合,而不是继承来达到代码复用的目的。换句话说,应该优先使用对象组合而不是类继承来实现代码复用。
示例:假设有一个Person
类,它需要具有name
和address
属性。按照合成复用原则,应该将name
和address
抽象为一个独立的类,然后Person
类通过组合的方式来使用。
// 合成复用原则的示例
public class Person {
private String name;
private Address address;
public Person(String name, Address address) {
this.name = name;
this.address = address;
}
}
public class Address {
private String street;
private String city;
public Address(String street, String city) {
this.street = street;
this.city = city;
}
}
设计模式分类
设计模式
├── 创建型模式(Creational Patterns):关注对象的创建过程。
│ ├── 单例模式(Singleton Pattern):确保一个类只有一个实例,并提供全局访问点。
│ ├── 工厂方法模式(Factory Method Pattern):定义一个用于创建对象的接口,但让子类决定实例化哪个类。
│ ├── 抽象工厂模式(Abstract Factory Pattern):提供一个接口,用于创建相关或依赖对象的家族,而不需要指定具体类。
│ ├── 建造者模式(Builder Pattern):将一个复杂对象的构建与其表示分离,使同样的构建过程可以创建不同的表示。
│ └── 原型模式(Prototype Pattern):通过复制现有对象来创建新对象,而不是通过实例化新对象。
├── 结构型模式(Structural Patterns):处理类和对象的组合,用于构建更大的结构。
│ ├── 适配器模式(Adapter Pattern):将一个类的接口转换成客户端希望的另一个接口。
│ ├── 装饰器模式(Decorator Pattern):动态地将责任附加到对象上,扩展其功能。
│ ├── 代理模式(Proxy Pattern):为其他对象提供一种代理以控制对这个对象的访问。
│ ├── 外观模式(Facade Pattern):为复杂子系统提供一个简化的接口。
│ ├── 桥接模式(Bridge Pattern):将抽象部分与实现部分分离,使它们可以独立地变化。
│ ├── 组合模式(Composite Pattern):将对象组合成树形结构以表示部分-整体层次结构。
│ └── 享元模式(Flyweight Pattern):通过共享尽可能多的相似对象来减少内存使用和提高性能。
└── 行为型模式(Behavioral Patterns):关注对象之间的通信和交互。
├── 观察者模式(Observer Pattern):定义对象间的一对多依赖关系,当一个对象改变状态时,它的所有依赖者都会收到通知并自动更新。
├── 状态模式(State Pattern):允许对象在内部状态改变时改变它的行为。
├── 策略模式(Strategy Pattern):定义一系列算法,封装每个算法,并使它们可以互换。
├── 模板方法模式(Template Method Pattern):定义算法的框架,将一些步骤延迟到子类中实现。
├── 职责链模式(Chain of Responsibility Pattern):为解决同一问题的一组对象提供解决方案,将这些对象串成一条链,并在该链上传递请求,直到有一个对象处理它。
├── 命令模式(Command Pattern):将请求封装成一个对象,从而允许用不同的请求对客户进行参数化。
├── 访问者模式(Visitor Pattern):在不改变元素类的前提下定义作用于这些元素的新操作。
├── 备忘录模式(Memento Pattern):在不破坏封装的前提下,捕获并保存一个对象的内部状态,以便稍后恢复它。
├── 中介者模式(Mediator Pattern):通过一个中介对象来封装一系列对象之间的交互。
└── 解释器模式(Interpreter Pattern):定义一个语言的文法,并定义一个解释器来解释该语言中的句子。
创建对象设计模式
面向对象编程需要大量创建对象,调用对象,使用设计模式,可以让对象的创建和管理更加高效
单例模式
单例模式是设计模式中的一种,用于确保某个类只有一个实例,并提供一个全局访问点。
单例类必须确保,在程序运行时,只创建一个对象
含义:
单例模式确保某个类只有一个实例,并提供一个全局访问点,以便于全局访问该实例。
特点:
- 类的构造方法是是私有的,不允许外界调用,即外界无法直接创建该类的对象
- 提供一个静态方法向外界暴露对象
作用:
- 确保唯一实例:单例模式确保了某个类只有一个实例存在,避免了多次实例化造成资源浪费的问题。
- 提供全局访问点:单例模式提供了一个全局访问点,可以在程序的任何地方访问该实例。
优点:
- 资源节约:单例模式避免了多次实例化造成的资源浪费。
- 全局访问点:通过单例模式提供的全局访问点,可以方便地在程序的任何地方访问该实例。
缺点:
- 可能引起性能问题:在高并发情况下,单例模式的实现可能会成为性能瓶颈。
- 可能引起线程安全问题:如果实现不当,单例模式可能会引起线程安全问题。
实现方案:
下面是几种常见的单例模式实现方案:
1. 懒汉式(Lazy Initialization)
线程不安全
在多线程情况下,对对象的判空操作将失去作用,也就无法确保只创建一个对象
特点:
- 只有首次调用获取单例对象的方法时,才会创建对象
解决方案:加同步锁
缺点:
- 高并发时耗时久
- 不管单例对象是否为空,其他线程都会被阻塞
public class Singleton {
private static Singleton instance;
private Singleton() {}
public static synchronized Singleton getInstance() {
if (instance == null) {
instance = new Singleton();
}
return instance;
}
}
2. 饿汉式(Eager Initialization)
特点:
- 在类初始化时,创建对象
缺点:
- 创建出来的单例对象不管会不会用到,都将占用内存空间,并且无法自动释放
public class Singleton {
private static final Singleton instance = new Singleton();
private Singleton() {}
public static Singleton getInstance() {
return instance;
}
}
3. 双重检查锁定(Double-Checked Locking)
特点:
- 进行两次判空操作,并在判空操作之间加同步锁
- volatile关键字,防止jvm重排指令
优点:
- 当单例对象不为空时,线程不需要等待同步锁释放,可以往下执行,直接拿到对象
public class Singleton {
private static volatile Singleton instance;
private Singleton() {}
public static Singleton getInstance() {
if (instance == null) {
synchronized (Singleton.class) {
if (instance == null) {
instance = new Singleton();
}
}
}
return instance;
}
}
4. 静态内部类(Static Inner Class)
特点:
- 在一个静态内部类中调用构造方法创建对象
- 在访问内部类的成员变量时才会触发内部类初始化
public class Singleton {
private Singleton() {}
private static class SingletonHolder {
private static final Singleton instance = new Singleton();
}
public static Singleton getInstance() {
return SingletonHolder.instance;
}
}
工厂模式
工厂模式是一种创建型设计模式,旨在提供一种统一的方式来创建对象,而无需指定具体的类。
它将对象的创建逻辑抽象出来,使得客户端在不需要知道具体实现类的情况下就能创建对象。
由来:
工厂模式最早由 Christopher Alexander 提出,在软件工程领域中,工厂模式被广泛应用。最早的工厂模式包括简单工厂模式、工厂方法模式和抽象工厂模式。
含义:
工厂模式的核心思想是将对象的实例化过程封装起来,提供一个统一的接口用于创建对象,使得客户端无需关心具体的实现类。
作用:
- 封装对象的创建逻辑:将对象的实例化过程与客户端解耦,客户端无需直接调用具体的实现类来创建对象。
- 提供灵活性:可以轻松地更改对象的创建逻辑,而无需修改客户端代码。
- 降低耦合度:客户端只需要知道工厂接口,无需知道具体的实现类,降低了客户端与具体实现类之间的耦合度。
优缺点:
-
优点:
- 提供了一种统一的接口来创建对象,使得客户端无需关心具体实现类。
- 可以轻松地更改对象的创建逻辑,而无需修改客户端代码。
- 降低了客户端与具体实现类之间的耦合度,增加了代码的灵活性和可维护性。
-
缺点:
- 增加了系统中类的个数,增加了系统的复杂度。
- 客户端需要知道工厂接口,可能会增加学习成本。
实现方案:
工厂模式主要包括简单工厂模式、工厂方法模式和抽象工厂模式。
-
简单工厂模式(Simple Factory Pattern):
-
简单工厂模式并不属于 GoF(Gang of Four)的23种经典设计模式,但它是工厂模式的最简单形式。
-
特点:
-
包括抽象产品,具体产品,具体工厂三个部分
-
通过一个静态方法创建对象,并且对象的类型是一个抽象接口
-
缺点:随着产品的增多,工厂类需要不断修改
-
示例代码:
// 简单工厂 public class SimpleFactory { public static Product createProduct(String type) { if ("A".equals(type)) { return new ConcreteProductA(); } else if ("B".equals(type)) { return new ConcreteProductB(); } return null; } } // 抽象产品 public interface Product { void show(); } // 具体产品 A public class ConcreteProductA implements Product { @Override public void show() { System.out.println("Product A"); } } // 具体产品 B public class ConcreteProductB implements Product { @Override public void show() { System.out.println("Product B"); } } // 客户端 public class Client { public static void main(String[] args) { Product productA = SimpleFactory.createProduct("A"); productA.show(); Product productB = SimpleFactory.createProduct("B"); productB.show(); } }
-
-
工厂方法模式(Factory Method Pattern):
-
工厂方法模式定义了一个用于创建对象的接口,让子类决定实例化哪一个类。
-
特点:
-
多了一个抽象工厂
-
抽象工厂中只有一个创建对象的方法,具体工厂将实现该方法,一个具体工厂只负责创建一类对象
-
缺点:每增加一个新产品,就需要编写一个具体工厂
-
示例代码:
// 工厂接口 public interface Factory { Product createProduct(); } // 具体工厂 A public class ConcreteFactoryA implements Factory { @Override public Product createProduct() { return new ConcreteProductA(); } } // 具体工厂 B public class ConcreteFactoryB implements Factory { @Override public Product createProduct() { return new ConcreteProductB(); } } // 客户端 public class Client { public static void main(String[] args) { Factory factoryA = new ConcreteFactoryA(); Product productA = factoryA.createProduct(); productA.show(); Factory factoryB = new ConcreteFactoryB(); Product productB = factoryB.createProduct(); productB.show(); } }
- 抽象工厂模式(Abstract Factory Pattern):
- 抽象工厂模式提供一个接口,用于创建一系列相关或相互依赖的对象,而无需指定它们的具体类。
- 特点:
- 抽象工厂拥有多个方法,一个方法创建一类对象
- 产品都继承一个抽象接口
- 示例代码:
// 抽象工厂接口
public interface AbstractFactory {
ProductA createProductA();
ProductB createProductB();
}
// 具体工厂 1
public class ConcreteFactory1 implements AbstractFactory {
@Override
public ProductA createProductA() {
return new ConcreteProductA1();
}
@Override
public ProductB createProductB() {
return new ConcreteProductB1();
}
}
// 具体工厂 2
public class ConcreteFactory2 implements AbstractFactory {
@Override
public ProductA createProductA() {
return new ConcreteProductA2();
}
@Override
public ProductB createProductB() {
return new ConcreteProductB2();
}
}
// 抽象产品 A
public interface ProductA {
void show();
}
// 具体产品 A1
public class ConcreteProductA1 implements ProductA {
@Override
public void show() {
System.out.println("Product A1");
}
}
// 具体产品 A2
public class ConcreteProductA2 implements ProductA {
@Override
public void show() {
System.out.println("Product A2");
}
}
// 抽象产品 B
public interface ProductB {
void show();
}
// 具体产品 B1
public class ConcreteProductB1 implements ProductB {
@Override
public void show() {
System.out.println("Product B1");
}
// 具体产品 B2
public class ConcreteProductB2 implements ProductB {
@Override
public void show() {
System.out.println("Product B2");
}
}
// 客户端
public class Client {
public static void main(String[] args) {
AbstractFactory factory1 = new ConcreteFactory1();
ProductA productA1 = factory1.createProductA();
ProductB productB1 = factory1.createProductB();
productA1.show();
productB1.show();
AbstractFactory factory2 = new ConcreteFactory2();
ProductA productA2 = factory2.createProductA();
ProductB productB2 = factory2.createProductB();
productA2.show();
productB2.show();
}
}
建造者模式
建造者模式(BuilderPattern)允许客户端选择构建过程的不同步骤来创建对象。
由来:
建造者模式最早由 Christopher Alexander 提出,用于解决建筑设计中的复杂性问题。后来,该模式被应用到软件工程领域,用于构建复杂对象的创建过程。
含义:
建造者模式的核心思想是将一个复杂对象的构建过程分解为多个简单的步骤,然后由指挥者按照特定的顺序调用这些步骤来构建对象,最终得到一个完整的对象。
作用:
- 分离构建过程与表示:将对象的构建过程与其表示分离,使得构建过程可以灵活地组合,而不影响最终的对象表示。
- 简化对象的创建:通过指定不同的构建步骤,客户端可以轻松地创建不同表示的对象。
- 提高代码的可读性和可维护性:将构建过程封装在具体的建造者类中,使得客户端代码更加清晰,易于理解和维护。
优缺点:
-
优点:
- 分离了构建过程与表示,使得构建过程可以灵活组合,客户端可以根据需要选择构建步骤。
- 简化了客户端的使用,客户端无需直接与复杂对象的构建过程打交道。
- 提高了代码的可读性和可维护性,将构建过程封装在具体的建造者类中,使得客户端代码更加清晰。
-
缺点:
- 增加了系统中类的个数,增加了系统的复杂度。
- 对于需要构建的对象较简单的情况,使用建造者模式可能会显得繁琐。
实现方案:
建造者模式主要包括标准建造者模式和流式建造者模式。
- 标准建造者模式:
特点:包括:抽象建造者,具体建造者,具体产品,指挥者
-
示例代码:
// 产品类 public class Product { private String partA; private String partB; private String partC; public void setPartA(String partA) { this.partA = partA; } public void setPartB(String partB) { this.partB = partB; } public void setPartC(String partC) { this.partC = partC; } // 其他操作 } // 建造者接口 public interface Builder { void buildPartA(); void buildPartB(); void buildPartC(); Product getResult(); } // 具体建造者 public class ConcreteBuilder implements Builder { private Product product = new Product(); @Override public void buildPartA() { product.setPartA("Part A"); } @Override public void buildPartB() { product.setPartB("Part B"); } @Override public void buildPartC() { product.setPartC("Part C"); } @Override public Product getResult() { return product; } } // 指挥者 public class Director { private Builder builder; public Director(Builder builder) { this.builder = builder; } public Product construct() { builder.buildPartA(); builder.buildPartB(); builder.buildPartC(); return builder.getResult(); } } // 客户端 public class Client { public static void main(String[] args) { Builder builder = new ConcreteBuilder(); Director director = new Director(builder); Product product = director.construct(); } }
- 流式建造者模式:
-
示例代码:
// 产品类 public class Product { private String partA; private String partB; private String partC; public Product() {} public Product(String partA, String partB, String partC) { this.partA = partA; this.partB = partB; this.partC = partC; } // Getter 和 Setter 方法 } // 建造者类 public class ProductBuilder { private String partA; private String partB; private String partC; public ProductBuilder partA(String partA) { this.partA = partA; return this; } public ProductBuilder partB(String partB) { this.partB = partB; return this; } public ProductBuilder partC(String partC) { this.partC = partC; return this; } public Product build() { return new Product(partA, partB, partC); } } // 客户端 public class Client { public static void main(String[] args) { Product product = new ProductBuilder() .partA("Part A") .partB("Part B") .partC("Part C") .build(); } }
-
类和对象的组合
代理模式
代理模式是一种结构型设计模式,其目的是为其他对象提供一种代理以控制对象的访问。代理对象充当原始对象的中间人,客户端通过代理对象访问原始对象,从而实现了对原始对象的间接访问和控制。
特点
- 必须保证,任何能够使用目标对象的地方,都能换成代理对象,换句话说,就是目标对象和代理对象的类型是一致的
由来
代理模式最初由Erich Gamma、Richard Helm、Ralph Johnson、John Vlissides在《设计模式:可复用面向对象软件的基础》一书中提出。
含义
代理模式的核心思想是引入一个代理对象,代理对象控制客户端对于原始对象的访问。代理对象充当了原始对象的“门卫”,负责处理与原始对象相关的操作。
作用
- 控制对原始对象的访问:代理对象可以限制对原始对象的访问,例如权限控制、访问控制等。
- 增强原始对象的功能:代理对象可以在调用原始对象的方法前后进行一些额外操作,例如记录日志、缓存数据等。
- 减轻系统负载:代理对象可以延迟加载原始对象,减轻系统启动时的负载。
优点
- 降低系统的耦合度:代理对象与原始对象解耦,客户端通过代理对象访问原始对象,不需要直接依赖于原始对象。
- 扩展性强:通过引入代理对象,可以在不修改原始对象的情况下增加新的功能。
- 保护原始对象:代理对象可以限制对原始对象的访问,提高系统的安全性。
缺点
- 增加代码复杂性:引入代理对象会增加代码量和复杂度。
- 可能会影响性能:在访问原始对象时,需要经过代理对象,可能会增加额外的开销。
实现方案
代理模式可以通过多种方式实现,常见的有以下几种:
- 静态代理:在编译时就已经确定代理关系,代理类和被代理类需要实现相同的接口或继承相同的父类。
- 动态代理:在运行时动态生成代理类,代理类不需要实现特定的接口或继承特定的父类。
- Cglib 代理:通过继承被代理类来生成代理对象,不需要实现接口。
- JDK 代理:基于接口的代理,被代理类必须实现一个接口,代理类通过实现 InvocationHandler 接口来实现对被代理方法的增强。
以下是一个简单的 Java 代码示例,演示了静态代理、动态代理和 Cglib 代理的实现:
// 被代理类
public interface Subject {
void request();
}
// 被代理类的实现
public class RealSubject implements Subject {
@Override
public void request() {
System.out.println("RealSubject: Processing request.");
}
}
// 静态代理
public class ProxySubject implements Subject {
private Subject realSubject;
public ProxySubject(Subject realSubject) {
this.realSubject = realSubject;
}
@Override
public void request() {
System.out.println("ProxySubject: Before request.");
realSubject.request();
System.out.println("ProxySubject: After request.");
}
}
// 动态代理
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
import java.lang.reflect.Proxy;
public class DynamicProxyHandler implements InvocationHandler {
private Object target;
public DynamicProxyHandler(Object target) {
this.target = target;
}
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
System.out.println("DynamicProxy: Before request.");
Object result = method.invoke(target, args);
System.out.println("DynamicProxy: After request.");
return result;
}
}
// Cglib 代理
import net.sf.cglib.proxy.Enhancer;
import net.sf.cglib.proxy.MethodInterceptor;
import net.sf.cglib.proxy.MethodProxy;
public class CglibProxy implements MethodInterceptor {
private Object target;
public CglibProxy(Object target) {
this.target = target;
}
public Object getProxyInstance() {
Enhancer enhancer = new Enhancer();
enhancer.setSuperclass(target.getClass());
enhancer.setCallback(this);
return enhancer.create();
}
@Override
public Object intercept(Object obj, Method method, Object[] args, MethodProxy proxy) throws Throwable {
System.out.println("CglibProxy: Before request.");
Object result = proxy.invokeSuper(obj, args);
System.out.println("CglibProxy: After request.");
return result;
}
}
// 测试代码
public class Main {
public static void main(String[] args) {
// 静态代理
Subject realSubject = new RealSubject();
ProxySubject proxySubject = new ProxySubject(realSubject);
proxySubject.request();
// 动态代理
Subject dynamicProxy = (Subject) Proxy.newProxyInstance(
Subject.class.getClassLoader(),
new Class[]{Subject.class},
new DynamicProxyHandler(realSubject)
);
dynamicProxy.request();
// Cglib 代理
CglibProxy cglibProxy = new CglibProxy(new RealSubject());
RealSubject cglibSubject = (RealSubject) cglibProxy.getProxyInstance();
cglibSubject.request();
}
}
JDK动态代理
原生JDK的Proxy类提供了运行中生成代理类以及代理对象的api
JDK 动态代理是 Java 提供的一种实现代理模式的方式,其原理是通过反射机制动态生成代理类,在运行时生成代理对象,而不需要在编译时确定代理类的类型。JDK 动态代理主要借助 java.lang.reflect.Proxy
类和 java.lang.reflect.InvocationHandler
接口来实现。
原理
- 定义接口:首先需要定义一个接口,该接口包含了需要被代理的方法。
- 实现 InvocationHandler 接口:定义一个类实现
InvocationHandler
接口,该类负责拦截对代理对象方法的调用,并在调用前后进行额外操作。 - 使用 Proxy 类创建代理对象:通过
Proxy.newProxyInstance(ClassLoader loader, Class<?>[] interfaces, InvocationHandler h)
方法动态创建代理对象。
代码示例
以下是一个简单的示例,演示了如何使用 JDK 动态代理:
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
import java.lang.reflect.Proxy;
// 定义接口
interface Subject {
void request();
}
// 实现 InvocationHandler 接口
class DynamicProxyHandler implements InvocationHandler {
private Object target;
public DynamicProxyHandler(Object target) {
this.target = target;
}
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
System.out.println("Before invoking...");
Object result = method.invoke(target, args);
System.out.println("After invoking...");
return result;
}
}
// 测试代码
public class Main {
public static void main(String[] args) {
// 创建被代理对象
Subject realSubject = new RealSubject();
// 创建动态代理对象
Subject proxy = (Subject) Proxy.newProxyInstance(
Subject.class.getClassLoader(),
new Class[]{Subject.class},
new DynamicProxyHandler(realSubject)
);
// 调用代理对象的方法
proxy.request();
}
}
优点
- 灵活性强:动态代理可以在运行时动态生成代理类,不需要在编译时确定代理类的类型,因此具有更高的灵活性。
- 代码简洁:相比静态代理,动态代理不需要手动编写代理类,代码更加简洁。
- 适用于接口代理:JDK 动态代理基于接口进行代理,适用于对接口方法的代理。
缺点
- 只能代理接口:由于 JDK 动态代理是基于接口进行代理的,因此只能代理实现了接口的类。
- 性能稍低:相比于静态代理,动态代理使用反射机制,会带来一定的性能损耗。
装饰器模式
装饰器模式是一种结构型设计模式,其目的是在不改变原有对象接口的情况下,动态地给对象添加额外的功能。它通过创建一个包装对象,也就是装饰器,来包裹原始对象,并且通过相同的接口来扩展其功能。
由来:
装饰器模式最早由 Gamma、Helm、Johnson 和 Vlissides 在《设计模式:可复用面向对象软件的基础》(GoF)一书中提出。该模式的灵感来自于结构复杂的 GUI 工具包。在这些工具包中,经常需要动态地为组件添加额外的功能,但又不希望创建大量的子类来实现每种组合,于是装饰器模式应运而生。
含义:
装饰器模式允许你通过包装一个类的对象来扩展其功能,而无需修改原始类的结构。这种方式可以在运行时动态地添加、删除或替换对象的行为。
作用:
- 动态地扩展对象功能: 装饰器模式允许在不改变对象接口的情况下,动态地给对象添加新的功能。
- 避免类爆炸: 装饰器模式可以代替继承,避免创建大量的子类来实现各种组合。
- 单一责任原则: 每个装饰器只关注于添加特定的功能,保持类的单一责任原则。
优缺点:
优点:
- 灵活性: 可以动态地给对象添加新的功能,且可以任意组合多个装饰器。
- 避免类爆炸: 不需要创建大量的子类来实现各种组合,避免类的爆炸问题。
- 单一责任原则: 每个装饰器只关注于一个特定的功能,符合单一责任原则。
缺点:
- 复杂性: 过多的装饰器可能会导致类的复杂度增加,使得代码难以理解和维护。
- 多层装饰: 当装饰器嵌套过多时,可能会影响性能。
实现方案:
下面是装饰器模式的一个简单示例,假设有一个咖啡店,需要为咖啡添加额外的配料(例如牛奶、糖等):
// 定义饮料接口
interface Beverage {
String getDescription();
double cost();
}
// 实现具体的饮料类
class Espresso implements Beverage {
public String getDescription() {
return "Espresso";
}
public double cost() {
return 1.99;
}
}
// 定义装饰器类
abstract class CondimentDecorator implements Beverage {
protected Beverage beverage;
public CondimentDecorator(Beverage beverage) {
this.beverage = beverage;
}
}
// 具体的装饰器类
class Milk extends CondimentDecorator {
public Milk(Beverage beverage) {
super(beverage);
}
public String getDescription() {
return beverage.getDescription() + ", Milk";
}
public double cost() {
return beverage.cost() + 0.5;
}
}
// 具体的装饰器类
class Sugar extends CondimentDecorator {
public Sugar(Beverage beverage) {
super(beverage);
}
public String getDescription() {
return beverage.getDescription() + ", Sugar";
}
public double cost() {
return beverage.cost() + 0.2;
}
}
// 测试
public class Main {
public static void main(String[] args) {
Beverage espresso = new Espresso();
System.out.println("Description: " + espresso.getDescription() + ", Cost: " + espresso.cost());
Beverage espressoWithMilk = new Milk(new Espresso());
System.out.println("Description: " + espressoWithMilk.getDescription() + ", Cost: " + espressoWithMilk.cost());
Beverage espressoWithMilkAndSugar = new Sugar(new Milk(new Espresso()));
System.out.println("Description: " + espressoWithMilkAndSugar.getDescription() + ", Cost: " + espressoWithMilkAndSugar.cost());
}
}
在这个示例中,Beverage
接口定义了饮料的基本操作,Espresso
是一个具体的饮料类。CondimentDecorator
是装饰器抽象类,Milk
和 Sugar
是具体的装饰器类。通过组合不同的装饰器,可以为咖啡添加不同的配料,而不需要修改原始的咖啡类。
适配器模式
适配器模式是一种结构型设计模式,其目的是允许接口不兼容的类能够相互合作。它通过创建一个包装类(适配器)来将一个类的接口转换成客户端所期望的另一个接口,从而使得原本由于接口不兼容而无法在一起工作的类能够协同工作。
特点:
适配器实现目标接口。
适配器和被适配的类是组合关系,适配器依赖被适配类的对象,该对象通过构造方法注入
由来:
适配器模式最早在《设计模式:可复用面向对象软件的基础》(GoF)一书中被提出。该模式的灵感来自于需要使用一个已经存在的类,但是它的接口与所需接口不匹配的情况。
含义:
适配器模式允许将一个类的接口转换成另一个接口,以满足客户端的需求。它使得原本无法协同工作的类能够相互合作,提高了代码的复用性和灵活性。
作用:
- 接口转换: 将一个类的接口转换成另一个接口,使得原本不兼容的类能够协同工作。
- 复用性: 可以重复使用现有的类,而无需修改其源代码。
- 解耦合: 通过适配器,客户端与被适配的类之间解耦,使得它们可以独立演化。
优缺点:
优点:
- 增加透明性: 客户端可以通过统一的接口与适配器交互,而不需要知道被适配者的具体实现。
- 增加复用性: 可以重复使用现有的类,而无需修改其源代码。
- 灵活性: 可以在不修改现有代码的情况下,引入新的功能。
缺点:
- 过多的适配器: 如果系统中存在大量的适配器,可能会导致类的数量剧增,增加了系统的复杂性。
- 过度设计: 如果设计不当,可能会引入过多的适配器,从而使系统变得复杂。
实现方案:
下面是适配器模式的一个简单示例,假设有一个音频播放器,它只能播放 MP3 格式的音频文件,但是现在需要播放其他格式的音频文件(例如 MP4、WAV 等):
// 定义目标接口
interface MediaPlayer {
void play(String audioType, String fileName);
}
// 实现具体的类
class MP3Player implements MediaPlayer {
public void play(String audioType, String fileName) {
if (audioType.equalsIgnoreCase("mp3")) {
System.out.println("Playing MP3 file: " + fileName);
} else {
System.out.println("Unsupported audio format: " + audioType);
}
}
}
// 定义适配器类
class MediaAdapter implements MediaPlayer {
private AdvancedMediaPlayer advancedMediaPlayer;
public MediaAdapter(String audioType) {
if (audioType.equalsIgnoreCase("mp4")) {
advancedMediaPlayer = new MP4Player();
} else if (audioType.equalsIgnoreCase("wav")) {
advancedMediaPlayer = new WAVPlayer();
}
}
public void play(String audioType, String fileName) {
if (advancedMediaPlayer != null) {
advancedMediaPlayer.playFile(fileName);
} else {
System.out.println("Unsupported audio format: " + audioType);
}
}
}
// 定义高级播放器接口
interface AdvancedMediaPlayer {
void playFile(String fileName);
}
// 实现具体的高级播放器类
class MP4Player implements AdvancedMediaPlayer {
public void playFile(String fileName) {
System.out.println("Playing MP4 file: " + fileName);
}
}
// 实现具体的高级播放器类
class WAVPlayer implements AdvancedMediaPlayer {
public void playFile(String fileName) {
System.out.println("Playing WAV file: " + fileName);
}
}
// 客户端代码
public class Main {
public static void main(String[] args) {
MediaPlayer mediaPlayer = new MP3Player();
mediaPlayer.play("mp3", "song.mp3");
// 使用适配器播放其他格式的音频文件
mediaPlayer = new MediaAdapter("mp4");
mediaPlayer.play("mp4", "video.mp4");
mediaPlayer = new MediaAdapter("wav");
mediaPlayer.play("wav", "sound.wav");
}
}
在这个示例中,MediaPlayer
接口定义了播放音频文件的方法,MP3Player
是一个具体的音频播放器类。AdvancedMediaPlayer
接口定义了高级播放器的方法,MP4Player
和 WAVPlayer
是具体的高级播放器类。MediaAdapter
是一个适配器类,它实现了 MediaPlayer
接口,并在内部使用了 AdvancedMediaPlayer
接口来播放其他格式的音频文件。通过适配器,原本只能播放 MP3 文件的播放器现在可以播放其他格式的音频文件了。
对象的协作
观察者模式
观察者模式是一种行为设计模式,用于定义对象之间的一对多依赖关系,使得当一个对象的状态发生变化时,其所有依赖者(观察者)都会得到通知并自动更新。该模式也被称为发布-订阅模式。
由来
观察者模式最早由美国计算机科学家Christopher Strachey提出,并在1977年的《ACM图灵奖演讲》中首次正式描述。
含义
观察者模式包括两种角色:主题(Subject)和观察者(Observer)。主题维护一组依赖于它的观察者对象,并提供添加、删除和通知观察者的方法。观察者则定义了一个更新方法,在主题状态发生变化时被调用。
作用
- 支持松耦合:主题和观察者之间的依赖是松散的,使得它们可以独立变化而互不影响。
- 提供一种通知机制:当主题状态发生变化时,所有的观察者都会自动收到通知并进行更新,确保对象间的一致性。
- 实现广播通信:主题可以向多个观察者同时发送通知,实现了一对多的通信模式。
优点
- 松耦合:主题和观察者之间的关系是松散的,使得它们可以独立变化。
- 可扩展性:可以方便地增加新的观察者和主题,而无需修改现有代码。
- 通知机制:当主题状态发生变化时,所有的观察者都会得到通知,实现了对象间的一致性。
缺点
- 如果观察者过多或者通知频繁,可能会导致性能问题。
- 如果观察者和主题之间有循环依赖,可能会导致死锁。
实现方案
以下是观察者模式的几种常见实现方案:
- 使用基本的观察者接口和主题类:
// 观察者接口
interface Observer {
void update();
}
// 主题类
class Subject {
private List<Observer> observers = new ArrayList<>();
public void addObserver(Observer observer) {
observers.add(observer);
}
public void removeObserver(Observer observer) {
observers.remove(observer);
}
public void notifyObservers() {
for (Observer observer : observers) {
observer.update();
}
}
}
- 使用Java内置的观察者模式:
import java.util.Observable;
import java.util.Observer;
// 观察者类
class ConcreteObserver implements Observer {
@Override
public void update(Observable o, Object arg) {
// 更新逻辑
}
}
// 主题类
class ConcreteSubject extends Observable {
public void change() {
setChanged();
notifyObservers();
}
}
- 使用事件监听器(Listener):
import java.util.EventListener;
import java.util.EventObject;
// 事件类
class MyEvent extends EventObject {
public MyEvent(Object source) {
super(source);
}
}
// 事件监听器接口
interface MyListener extends EventListener {
void onEvent(MyEvent event);
}
// 主题类
class MySubject {
private List<MyListener> listeners = new ArrayList<>();
public void addListener(MyListener listener) {
listeners.add(listener);
}
public void removeListener(MyListener listener) {
listeners.remove(listener);
}
public void fireEvent() {
MyEvent event = new MyEvent(this);
for (MyListener listener : listeners) {
listener.onEvent(event);
}
}
}
模板方法模式
模板方法模式
由来
模板方法模式最早由美国计算机科学家Christopher Strachey提出,并在1977年的《ACM图灵奖演讲》中首次正式描述。
含义
模板方法模式是一种行为设计模式,用于定义一个操作中的算法骨架,而将一些步骤延迟到子类中。它使得子类可以在不改变算法结构的情况下,重新定义算法中的某些步骤。
作用
- 定义算法框架:将算法的通用步骤定义在父类中,具体步骤的实现延迟到子类中。
- 提高代码复用性:避免了重复编写相似的代码,将共同部分提取到父类中。
- 便于扩展:子类可以灵活地扩展或修改算法中的某些步骤,而不影响整个算法的结构。
优点
- 提高了代码复用性和扩展性。
- 将算法中的共同部分提取到父类中,避免了代码重复。
缺点
- 可能导致子类数量增加,增加了系统的复杂性。
- 可能违反了“单一职责原则”,使得父类中的方法过于庞大。
实现方案
以下是模板方法模式的几种常见实现方案:
- 基本实现:
// 抽象模板类
abstract class AbstractTemplate {
public void templateMethod() {
step1();
step2();
step3();
}
protected abstract void step1();
protected abstract void step2();
protected abstract void step3();
}
// 具体子类
class ConcreteTemplate extends AbstractTemplate {
@Override
protected void step1() {
// 实现步骤1
}
@Override
protected void step2() {
// 实现步骤2
}
@Override
protected void step3() {
// 实现步骤3
}
}
- 钩子方法:
// 抽象模板类
abstract class AbstractTemplate {
public void templateMethod() {
step1();
if (hook()) {
step2();
}
step3();
}
protected abstract void step1();
protected abstract void step2();
protected abstract void step3();
protected boolean hook() {
return true;
}
}
// 具体子类
class ConcreteTemplate extends AbstractTemplate {
@Override
protected void step1() {
// 实现步骤1
}
@Override
protected void step2() {
// 实现步骤2
}
@Override
protected void step3() {
// 实现步骤3
}
@Override
protected boolean hook() {
// 子类可选择重写钩子方法
return false;
}
}
- 使用回调函数:
// 抽象模板类
abstract class AbstractTemplate {
public void templateMethod(Callback callback) {
step1();
callback.execute();
step3();
}
protected abstract void step1();
protected abstract void step3();
}
// 回调接口
interface Callback {
void execute();
}
// 具体子类
class ConcreteTemplate extends AbstractTemplate {
@Override
protected void step1() {
// 实现步骤1
}
@Override
protected void step3() {
// 实现步骤3
}
}
// 回调实现类
class ConcreteCallback implements Callback {
@Override
public void execute() {
// 实现回调逻辑
}
}
迭代器模式
由来
迭代器模式最早由美国计算机科学家Christopher Strachey和Peter J. Landin在1960年代提出,并在1972年由Donald E. Knuth在其著作《计算机程序设计艺术》中首次详细描述。
原理
迭代器模式提供了一种顺序访问集合对象元素的方法,而不需要暴露其内部结构。它将迭代过程抽象化,使得客户端可以统一地访问集合对象中的每一个元素,而无需关心集合内部的数据结构。
含义
迭代器模式是一种行为设计模式,用于提供一种方法来访问聚合对象中的各个元素,而无需暴露该对象的内部表示。
作用
- 提供统一的访问接口:客户端可以通过迭代器统一访问聚合对象中的元素,而不需要了解其内部结构。
- 封装集合对象的遍历逻辑:将遍历逻辑封装在迭代器中,使得客户端可以简化遍历过程。
- 支持多种遍历方式:可以定义不同类型的迭代器,实现不同的遍历方式,如顺序遍历、逆序遍历等。
优点
- 分离了集合对象和遍历行为,使得代码更加清晰和易于维护。
- 提供了多种遍历方式的支持,增加了灵活性。
缺点
- 增加了系统的复杂性,引入了额外的类和接口。
- 在某些情况下,可能会降低性能。
实现方案
以下是迭代器模式的几种常见实现方案:
- 基本实现:
// 迭代器接口
interface Iterator<T> {
boolean hasNext();
T next();
}
// 聚合接口
interface Aggregate<T> {
Iterator<T> iterator();
}
// 具体迭代器实现
class ConcreteIterator<T> implements Iterator<T> {
private int index = 0;
private List<T> elements;
public ConcreteIterator(List<T> elements) {
this.elements = elements;
}
@Override
public boolean hasNext() {
return index < elements.size();
}
@Override
public T next() {
if (!hasNext()) {
throw new NoSuchElementException();
}
return elements.get(index++);
}
}
// 具体聚合实现
class ConcreteAggregate<T> implements Aggregate<T> {
private List<T> elements = new ArrayList<>();
public void add(T element) {
elements.add(element);
}
@Override
public Iterator<T> iterator() {
return new ConcreteIterator<>(elements);
}
}
// 客户端代码
public class Client {
public static void main(String[] args) {
ConcreteAggregate<String> aggregate = new ConcreteAggregate<>();
aggregate.add("A");
aggregate.add("B");
aggregate.add("C");
Iterator<String> iterator = aggregate.iterator();
while (iterator.hasNext()) {
System.out.println(iterator.next());
}
}
}
- 使用内部类实现迭代器:
// 具体聚合实现
class ConcreteAggregate<T> implements Aggregate<T> {
private List<T> elements = new ArrayList<>();
public void add(T element) {
elements.add(element);
}
@Override
public Iterator<T> iterator() {
return new Iterator<T>() {
private int index = 0;
@Override
public boolean hasNext() {
return index < elements.size();
}
@Override
public T next() {
if (!hasNext()) {
throw new NoSuchElementException();
}
return elements.get(index++);
}
};
}
}
// 客户端代码与上例相同
- 使用Java内置迭代器:
// 具体聚合实现
class ConcreteAggregate<T> implements Aggregate<T> {
private List<T> elements = new ArrayList<>();
public void add(T element) {
elements.add(element);
}
@Override
public Iterator<T> iterator() {
return elements.iterator();
}
}
// 客户端代码与上例相同