1. 重要度从高到低:
- 组合 or 装饰
- 状态 or 策略
- 适配器 + 桥接(或其他)or 外观
- 观察者 or 中介者
- 抽象工厂 or 建造者(大概率抽象工厂)
- 其他小点:原型,代理,单例,命令,职责链,迭代器
2. 很少考察的内容:
- 模板方法不单独考(大概率配合策略模式)
- 简单工厂和工厂方法大概率被抽象工厂替代考察
- 迭代器和命令难度较高(没考过)
3. 多模式混用:
- 适配器 + 其他(大概率桥接)
- 模板方法 + 策略模式
- 装饰 + 组合
- 命令模式 + 其他(难度较高)
定义:软件实体(类、模块、函数等)应该对扩展开放,对修改关闭。
解释:需要对某个功能进行扩展时,不应该修改已有的代码,而应该通过增加新的代码。
举例:如果要添加一个三角形的面积计算功能,应该通过扩展一个新的类(如 TriangleAreaCalculator),而不是直接修改现有的 Shape 类。
定义:子类对象必须能够替换掉父类对象,并且程序的行为不会发生变化。
解释:子类应该能够在任何使用父类的地方使用。即父类不应该有子类没实现的功能。
举例:如果我们有一个父类 Bird,其中有一个方法 fly(),那么如果我们创建了一个 Penguin 子类,它不应该继承 fly() 方法(因为企鹅不能飞)。
定义:也称为"最少知识原则",一个对象应该尽可能少地了解其他对象。
解释:不希望陌生的类之间建立直接的联系。如果真的有需要建立联系,也希望能通过它的友元类来转达。
举例:假设有如下代码:a.getB().getC().method()。更好的方式是让 a 对象直接提供一个方法调用 method(),而不是通过 B 和 C 对象间接调用。
定义:一个类应该只有一个引起变化的原因,或者说一个类应该只负责一件事。
举例:假设有一个类 UserManager,它同时负责用户的管理和日志记录。根据单一职责原则,应该将日志记录的功能提取到一个独立的 Logger 类中,这样 UserManager 只负责用户管理。
定义:客户端不应该被迫依赖它不使用的接口。
举例:假设有一个接口 Worker,其中包含 work() 和 eat() 方法。如果 robot(机器人不需要吃饭)只需要 work() 方法而不需要 eat() 方法,那么应该将接口拆分为 Worker 和 Eater 两个接口。
定义:高层模块不应该依赖于低层模块,二者都应该依赖于抽象。
解释:高层模块不应该直接依赖于低层模块的具体实现,而应该依赖于抽象。
定义:尽量使用组合或聚合的方式,而不是继承的方式来实现复用。
设计模式是在特定环境下解决特定问题的一套被反复使用的、经过分类编目的、代码设计经验的总结。
设计模式的基本要素:
- 模式名称 (Pattern name):一个助记名,它用一两个词来描述模式的问题、解决方案和效果。
- 问题 (Problem):描述了应该在何时使用模式。
- 解决方案 (Solution):描述了设计的组成部分,它们之间的相互关系以及各自的职责和协作方式。
- 效果 (Consequences):描述了模式应用的效果及使用模式应权衡的问题。
- 创建型模式:关注对象的创建过程,包括工厂模式、抽象工厂模式、单例模式、建造者模式和原型模式。
- 结构型模式:关注对象和类的组合,包括适配器模式、桥接模式、组合模式、装饰模式、外观模式、享元模式和代理模式。
- 行为型模式:关注对象之间的通信,包括责任链模式、命令模式、解释器模式、迭代器模式、中介者模式、备忘录模式、观察者模式、状态模式、策略模式、模板方法模式和访问者模式。
- 类模式:处理类和子类之间的关系,这些关系通过继承建立,是静态的,在编译时就确定下来了。
- 对象模式:处理对象之间的关系,这些关系在运行时可以变化,更具动态性。
GoF(Gang of Four,四人组)设计模式共有23种,分为三大类:
- 工厂方法模式 (Factory Method)
- 抽象工厂模式 (Abstract Factory)
- 单例模式 (Singleton)
- 建造者模式 (Builder)
- 原型模式 (Prototype)
- 适配器模式 (Adapter)
- 桥接模式 (Bridge)
- 组合模式 (Composite)
- 装饰模式 (Decorator)
- 外观模式 (Facade)
- 享元模式 (Flyweight)
- 代理模式 (Proxy)
- 责任链模式 (Chain of Responsibility)
- 命令模式 (Command)
- 解释器模式 (Interpreter)
- 迭代器模式 (Iterator)
- 中介者模式 (Mediator)
- 备忘录模式 (Memento)
- 观察者模式 (Observer)
- 状态模式 (State)
- 策略模式 (Strategy)
- 模板方法模式 (Template Method)
- 访问者模式 (Visitor)
创建型模式关注对象的创建过程,通过将对象的创建和使用分离,来提高系统的灵活性和可复用性。
- 封装对象创建的细节
- 隐藏对象创建的复杂性
- 提供对象创建的统一接口
- 支持对象创建的灵活性
简单工厂模式通过一个工厂类来创建对象,客户端不需要知道具体的类名,只需要知道相应的参数。
// 简单工厂模式和工厂方式模式都是类模式,是静态的
public class TVFactory {
public static TV produceTV(String brand) throws Exception {
if (brand.equalsIgnoreCase("Haier")) {
System.out.println("电视机工厂生产海尔电视机!");
return new HaierTV();
} else if (brand.equalsIgnoreCase("Hisense")) {
System.out.println("电视机工厂生产海信电视机!");
return new HisenseTV();
} else {
throw new Exception("对不起,暂不能生产该品牌电视机!");
}
}
}
public interface TV {
void play();
}
public class HaierTV implements TV {
public void play() {
System.out.println("海尔电视机播放中......");
}
}
public class HisenseTV implements TV {
public void play() {
System.out.println("海信电视机播放中......");
}
}1. 优点:
- 实现了对象创建和使用的分离
- 客户端不需要知道具体产品的类名,只需要知道参数
- 通过引入配置文件,可以在不修改客户端代码的情况下更换和增加新的具体产品类
2. 缺点:
- 工厂类集中了所有产品的创建逻辑,职责过重
- 当需要添加新产品时,需要修改工厂类的代码,违反了开闭原则
- 简单工厂模式使用静态工厂方法,无法通过继承来改变创建方法的行为
3. 适用场景:
- 工厂类负责创建的对象比较少
- 客户端只知道传入工厂类的参数,对于如何创建对象不关心
工厂方法模式定义了一个创建对象的接口,但由子类决定要实例化的类是哪一个。工厂方法让类把实例化推迟到子类。
// 抽象产品
public interface TV {
void play();
}
// 具体产品
public class HaierTV implements TV {
public void play() {
System.out.println("海尔电视机播放中......");
}
}
public class HisenseTV implements TV {
public void play() {
System.out.println("海信电视机播放中......");
}
}
// 抽象工厂
public interface TVFactory {
TV produceTV();
}
// 具体工厂
public class HaierTVFactory implements TVFactory {
public TV produceTV() {
System.out.println("海尔电视机工厂生产海尔电视机!");
return new HaierTV();
}
}
public class HisenseTVFactory implements TVFactory {
public TV produceTV() {
System.out.println("海信电视机工厂生产海信电视机!");
return new HisenseTV();
}
}
// 客户端
public class Client {
public static void main(String[] args) {
TVFactory factory = new HaierTVFactory();
TV tv = factory.produceTV();
tv.play();
}
}1. 优点:
- 客户端不需要知道具体产品的类名,只需要知道对应的工厂
- 扩展性好,增加新产品时只需要添加对应的具体产品类和具体工厂类
- 符合开闭原则,无需修改现有代码
2. 缺点:
- 增加了系统的复杂度,需要编写更多的类
- 客户端需要知道所有的工厂类,并决定使用哪一个
3. 适用场景:
- 客户端不知道它所需要的对象的类
- 抽象工厂类通过其子类来指定创建哪个对象
抽象工厂模式提供一个接口,用于创建相关或依赖对象的家族,而不需要明确指定具体类。
// 抽象产品族
public interface TV {
void play();
}
public interface Refrigerator {
void refrigerate();
}
// 具体产品
public class HaierTV implements TV {
public void play() {
System.out.println("海尔电视机播放中......");
}
}
public class HaierRefrigerator implements Refrigerator {
public void refrigerate() {
System.out.println("海尔冰箱制冷中......");
}
}
public class HisenseTV implements TV {
public void play() {
System.out.println("海信电视机播放中......");
}
}
public class HisenseRefrigerator implements Refrigerator {
public void refrigerate() {
System.out.println("海信冰箱制冷中......");
}
}
// 抽象工厂
public interface HomeApplianceFactory {
TV produceTV();
Refrigerator produceRefrigerator();
}
// 具体工厂
public class HaierFactory implements HomeApplianceFactory {
public TV produceTV() {
System.out.println("海尔工厂生产电视机!");
return new HaierTV();
}
public Refrigerator produceRefrigerator() {
System.out.println("海尔工厂生产冰箱!");
return new HaierRefrigerator();
}
}
public class HisenseFactory implements HomeApplianceFactory {
public TV produceTV() {
System.out.println("海信工厂生产电视机!");
return new HisenseTV();
}
public Refrigerator produceRefrigerator() {
System.out.println("海信工厂生产冰箱!");
return new HisenseRefrigerator();
}
}
// 客户端
public class Client {
public static void main(String[] args) {
HomeApplianceFactory factory = new HaierFactory();
TV tv = factory.produceTV();
Refrigerator refrigerator = factory.produceRefrigerator();
tv.play();
refrigerator.refrigerate();
}
}1. 模式优点:
- 隔离了具体类的生成,使得客户端不需要知道什么被创建
- 当一个产品族中的多个对象被设计成一起工作时,它能保证客户端始终只使用同一个产品族中的对象
- 增加新的产品族很方便,无需修改现有代码,符合开闭原则
2. 模式缺点:
- 增加新的产品等级结构很困难,需要修改抽象工厂和所有的具体工厂类
- 产品族扩展非常困难,要增加一个系列的某一产品,既要在抽象工厂里加代码,又要在具体工厂里加代码
3. 适用环境:
- 一个系统不应当依赖于产品类实例如何被创建、组合和表达的细节
- 系统中有多于一个的产品族,但每次只使用其中某一产品族
- 属于同一个产品族的产品将在一起使用,这一约束必须在系统的设计中体现出来
- 系统提供一个产品类的库,所有的产品以同样的接口出现,从而使客户端不依赖于具体实现
建造者模式将一个复杂对象的构建与它的表示分离,使得同样的构建过程可以创建不同的表示。
// 产品类,表示套餐
public class Meal {
// 套餐的部件:食物和饮料
private String food;
private String drink;
public void setFood(String food) {
this.food = food;
}
public void setDrink(String drink) {
this.drink = drink;
}
public String getFood() {
return this.food;
}
public String getDrink() {
return this.drink;
}
}
// 抽象建造者类,定义构建流程的接口
public abstract class MealBuilder {
// 需要构建的套餐对象,因此其和 Meal 是组合关系
protected Meal meal = new Meal();
public abstract void buildFood();
public abstract void buildDrink();
// 返回构建好的套餐
public Meal getMeal() {
return meal;
}
}
// 具体建造者A,实现套餐A的构建
public class SubMealBuilderA extends MealBuilder {
public void buildFood() {
meal.setFood("一个鸡腿堡");
}
public void buildDrink() {
meal.setDrink("一杯可乐");
}
}
// 具体建造者B,实现套餐B的构建
public class SubMealBuilderB extends MealBuilder {
public void buildFood() {
meal.setFood("一个鸡肉卷");
}
public void buildDrink() {
meal.setDrink("一杯果汁");
}
}
// 指挥者类
public class KFCWaiter {
private MealBuilder mb;
public void setMealBuilder(MealBuilder mb) {
this.mb = mb;
}
// 构建套餐并返回
public Meal construct() {
mb.buildFood(); // 构建主食
mb.buildDrink(); // 构建饮料
return mb.getMeal(); // 返回最终构建的套餐
}
}
// 客户端
public class Client {
public static void main(String args[]) {
KFCWaiter waiter = new KFCWaiter();
MealBuilder mb = new SubMealBuilderA();
waiter.setMealBuilder(mb);
Meal meal = waiter.construct();
System.out.println("套餐组成:");
System.out.println(meal.getFood());
System.out.println(meal.getDrink());
}
}1. 优点:
- 封装性好,构建和表示分离
- 扩展性好,各个具体的建造者相互独立,有利于系统的解耦
- 客户端不必知道产品内部组成的细节,建造者可以对创建过程逐步细化,而不对其它模块产生任何影响
2. 缺点:(建造者类复杂)
- 产品必须有共同点,范围有限制
- 如果内部变化复杂,会有很多的建造类
3. 适用场景:
- 需要生成的产品对象有复杂的内部结构,这些产品对象通常包含多个成员属性
- 需要生成的产品对象的属性相互依赖,需要指定其生成顺序
- 对象的创建过程独立于创建该对象的类
- 隔离复杂对象的创建和使用,并使得相同的创建过程可以创建不同的产品
原型模式通过复制对象自身来创建多个相同对象,适用于复杂且频繁创建的场景,节省资源。
不需关心对象类型,直接复制原型对象。符合里氏替换原则,即子类能实现父类所有的功能。
原型模式:通过原型实例指定对象种类,并复制原型创建新对象,无需了解创建细节。
优点:
- 快速创建对象:能够通过克隆快速创建大量相同或相似的对象,简化了对象的创建过程。
- 保存对象状态:可以保存对象的中间状态,并基于该状态创建新的对象。
缺点:
- 需要改造现有类:每个类都需要实现克隆方法,改造现有类时可能需要修改源代码,不满足开闭原则。
- 深克隆复杂:如下代码,确实复杂。
适用场景:
- 创建成本较大:当创建新对象的成本较大时,通过克隆现有对象来创建新对象可以节省开销。
- 对象状态变化小:系统需要保存对象的状态,且状态变化较小时,可以使用相似对象的复制。
- 避免复杂的工厂模式:当不希望使用分层次的工厂类来创建对象时,原型模式可以通过复制原型对象来简化对象的创建过程。
分成两种:浅拷贝、深拷贝的区别如下下图所示
核心区别是实现的接口不一样,浅拷贝是Cloneable,深拷贝是Serializable,且深拷贝组合的类也需要实现Serializable
// 附件类
public class Attachment {
// 模拟下载附件的方法
public void download() {
System.out.println("下载附件");
}
}
// 邮件类,实现了 Cloneable 接口,支持克隆
public class Email implements Cloneable {
private Attachment attachment = null;
public Email() {
this.attachment = new Attachment();
}
// 克隆方法,返回当前对象的浅拷贝
public Object clone() {
Email clone = null;
try {
// 调用父类的 clone() 方法进行浅拷贝
clone = (Email) super.clone();
} catch (CloneNotSupportedException e) {
System.out.println("Clone failure!"); // 如果克隆失败,输出错误信息
}
return clone;
}
// 获取附件的方法
public Attachment getAttachment() {
return this.attachment;
}
// 显示邮件内容的方法
public void display() {
System.out.println("查看邮件");
}
}
// 客户端类,测试克隆功能
public class Client {
public static void main(String a[]) {
Email email, copyEmail;
email = new Email();
// 克隆原始邮件
copyEmail = (Email) email.clone();
// 对象是深拷贝
System.out.println("email==copyEmail?");
System.out.println(email == copyEmail); // false
// 附件是同一个对象的浅拷贝
System.out.println("email.getAttachment==copyEmail.getAttachment?");
System.out.println(email.getAttachment() == copyEmail.getAttachment()); // true
}
}import java.io.*;
// 附件类,需要实现 Serializable
public class Attachment implements Serializable {
public void download() {
System.out.println("下载附件");
}
}
// 邮件类,实现了 Serializable 接口,支持深克隆
public class Email implements Serializable {
private Attachment attachment = null;
public Email() {
this.attachment = new Attachment();
}
// 深克隆实现方法
public Object deepClone() throws IOException, ClassNotFoundException {
// 将对象写入字节数组输出流
ByteArrayOutputStream bao = new ByteArrayOutputStream();
ObjectOutputStream oos = new ObjectOutputStream(bao);
oos.writeObject(this); // 序列化当前对象到流中
// 从字节数组输入流中读取对象
ByteArrayInputStream bis = new ByteArrayInputStream(bao.toByteArray());
ObjectInputStream ois = new ObjectInputStream(bis);
return ois.readObject(); // 反序列化生成新的对象
}
// 获取附件的方法
public Attachment getAttachment() {
return this.attachment;
}
// 显示邮件内容的方法
public void display() {
System.out.println("查看邮件");
}
}
// 客户端类
public class Client {
public static void main(String a[]) {
Email email, copyEmail = null;
email = new Email();
try {
// 深克隆 email 对象,区别就在 deep
copyEmail = (Email) email.deepClone();
} catch (Exception e) {
e.printStackTrace();
}
// 对象不是一个对象
System.out.println("email==copyEmail?");
System.out.println(email == copyEmail); // false,表示是不同的对象
// 附件对象不是一个附件对象
System.out.println("email.getAttachment==copyEmail.getAttachment?");
System.out.println(email.getAttachment() == copyEmail.getAttachment()); // false,表示附件也是不同的对象
}
}结构型模式关注对象和类的组合,通过继承或组合来组合接口或实现,以实现新的功能。
适配器模式将一个类的接口转换成客户希望的另外一个接口。适配器模式使得原本由于接口不兼容而不能一起工作的那些类可以一起工作。
类适配器通过继承来实现适配,适配器类继承了被适配者类并实现了目标接口。
// 目标接口 (客户端期望的接口)
public interface Target {
void request();
}
// 被适配者类 (旧的接口)
public class Adaptee {
public void specificRequest() {
System.out.println("Adaptee: Handling specific request");
}
}
// 适配器类 (实现了Target接口,适配Adaptee)
public class Adapter extends Adaptee implements Target {
public void request() {
specificRequest(); // 把客户端的request方法适配为Adaptee的specificRequest
}
}
// 客户端类
public class Client {
public static void main(String[] args) {
Target target = new Adapter(); // 客户端通过Target接口使用Adapter
target.request(); // 实际上调用的是Adaptee的specificRequest方法
}
}1. 灵活性更强 由于适配器类是适配者类的子类,因此可以在适配器类中置换一些适配者的方法,使得适配器的灵活性更强。
1. 多重继承限制,对于不支持多重继承的语言(如 Java、C#),类适配器模式一次最多只能适配一个适配者类。
对象适配器通过组合来实现适配,适配器类持有被适配者类的引用并实现了目标接口。
// 目标接口 (客户端期望的接口)
public interface Target {
void request();
}
// 被适配者类 (旧的接口)
public class Adaptee {
public void specificRequest() {
System.out.println("Adaptee: Handling specific request");
}
}
// 适配器类 (通过组合方式实现适配)
public class Adapter implements Target {
private Adaptee adaptee; // 适配器持有被适配者的引用
// 构造函数,传入被适配者的实例
public Adapter(Adaptee adaptee) {
this.adaptee = adaptee;
}
public void request() {
adaptee.specificRequest(); // 调用被适配者的方法
}
}
// 客户端类
public class Client {
public static void main(String[] args) {
Adaptee adaptee = new Adaptee();
Target target = new Adapter(adaptee);
target.request(); // 实际上调用的是Adaptee的specificRequest方法
}
}1. 适配多个类,一个对象适配器可以将多个不同的适配者适配到同一个目标,即同一个适配器可以将适配者类及其子类都适配到目标接口。
1. 增加设计难度,在适配器中置换适配者类的某些方法比较麻烦,如果一定要置换,就需要先创建一个适配者类的子类,将适配者类的方法置换掉,然后再把适配者类的子类作为真正的适配者进行适配。
装饰模式动态地给一个对象添加一些额外的职责。就增加功能来说,装饰模式相比生成子类更为灵活。
// 抽象构件
public interface Shape {
void draw();
}
// 具体构件
public class Circle implements Shape {
public void draw() {
System.out.println("Shape: Circle");
}
}
public class Rectangle implements Shape {
public void draw() {
System.out.println("Shape: Rectangle");
}
}
// 抽象装饰类
public abstract class ShapeDecorator implements Shape {
protected Shape decoratedShape;
public ShapeDecorator(Shape decoratedShape) {
this.decoratedShape = decoratedShape;
}
public void draw() {
decoratedShape.draw();
}
}
// 具体装饰类
public class RedShapeDecorator extends ShapeDecorator {
public RedShapeDecorator(Shape decoratedShape) {
super(decoratedShape);
}
@Override
public void draw() {
decoratedShape.draw();
setRedBorder(decoratedShape);
}
private void setRedBorder(Shape decoratedShape) {
System.out.println("Border Color: Red");
}
}
public class GreenShapeDecorator extends ShapeDecorator {
public GreenShapeDecorator(Shape decoratedShape) {
super(decoratedShape);
}
@Override
public void draw() {
decoratedShape.draw();
setGreenBorder(decoratedShape);
}
private void setGreenBorder(Shape decoratedShape) {
System.out.println("Border Color: Green");
}
}
// 客户端
public class Client {
public static void main(String[] args) {
Shape circle = new Circle();
Shape redCircle = new RedShapeDecorator(new Circle());
Shape greenRectangle = new GreenShapeDecorator(new Rectangle());
System.out.println("Circle with normal border");
circle.draw();
System.out.println("\nCircle of red border");
redCircle.draw();
System.out.println("\nRectangle of green border");
greenRectangle.draw();
}
}1. 优点:
- 装饰模式与继承关系的目的都是要扩展对象的功能,但是装饰模式可以提供比继承更多的灵活性
- 通过使用不同的装饰类以及这些装饰类的排列组合,可以创造出很多不同行为的组合
- 装饰模式完全遵守开闭原则
2. 缺点:
- 会产生很多细粒度的对象,这些对象仅仅在他们相互连接的方式上有所不同,而不是它们的类或是它们的属性值有所不同
3. 适用场景:
- 在不影响其他对象的情况下,以动态、透明的方式给单个对象添加职责
- 需要动态地给一个对象增加功能,这些功能也可以动态地被撤销
- 当不能采用生成子类的方法进行扩充时
命令模式将一个请求封装为一个对象,从而使你可用不同的请求对客户进行参数化;对请求排队或记录请求日志,以及支持可撤销的操作。
// 抽象命令
public interface Command {
void execute();
void undo();
}
// 具体命令1
public class LightOnCommand implements Command {
private Light light;
public LightOnCommand(Light light) {
this.light = light;
}
public void execute() {
light.on();
}
public void undo() {
light.off();
}
}
// 具体命令2
public class LightOffCommand implements Command {
private Light light;
public LightOffCommand(Light light) {
this.light = light;
}
public void execute() {
light.off();
}
public void undo() {
light.on();
}
}
// 接收者
public class Light {
private String location;
public Light(String location) {
this.location = location;
}
public void on() {
System.out.println(location + " light is on");
}
public void off() {
System.out.println(location + " light is off");
}
}
// 调用者
public class RemoteControl {
private Command[] onCommands;
private Command[] offCommands;
private Command undoCommand;
public RemoteControl() {
onCommands = new Command[7];
offCommands = new Command[7];
Command noCommand = new NoCommand();
for (int i = 0; i < 7; i++) {
onCommands[i] = noCommand;
offCommands[i] = noCommand;
}
undoCommand = noCommand;
}
public void setCommand(int slot, Command onCommand, Command offCommand) {
onCommands[slot] = onCommand;
offCommands[slot] = offCommand;
}
public void onButtonWasPushed(int slot) {
onCommands[slot].execute();
undoCommand = onCommands[slot];
}
public void offButtonWasPushed(int slot) {
offCommands[slot].execute();
undoCommand = offCommands[slot];
}
public void undoButtonWasPushed() {
undoCommand.undo();
}
}
// 空命令
public class NoCommand implements Command {
public void execute() {}
public void undo() {}
}
// 客户端
public class Client {
public static void main(String[] args) {
RemoteControl remoteControl = new RemoteControl();
Light livingRoomLight = new Light("Living Room");
Light kitchenLight = new Light("Kitchen");
LightOnCommand livingRoomLightOn = new LightOnCommand(livingRoomLight);
LightOffCommand livingRoomLightOff = new LightOffCommand(livingRoomLight);
LightOnCommand kitchenLightOn = new LightOnCommand(kitchenLight);
LightOffCommand kitchenLightOff = new LightOffCommand(kitchenLight);
remoteControl.setCommand(0, livingRoomLightOn, livingRoomLightOff);
remoteControl.setCommand(1, kitchenLightOn, kitchenLightOff);
remoteControl.onButtonWasPushed(0);
remoteControl.offButtonWasPushed(0);
remoteControl.undoButtonWasPushed();
remoteControl.offButtonWasPushed(1);
remoteControl.onButtonWasPushed(1);
}
}观察者模式定义了一种一对多的依赖关系,让多个观察者对象同时监听某一个主题对象。这个主题对象在状态发生变化时,会通知所有观察者对象,使它们能够自动更新自己。
// 抽象主题
public interface Subject {
void registerObserver(Observer observer);
void removeObserver(Observer observer);
void notifyObservers();
}
// 具体主题
public class ConcreteSubject implements Subject {
private List<Observer> observers = new ArrayList<>();
private String state;
public void setState(String state) {
this.state = state;
notifyObservers();
}
public String getState() {
return state;
}
public void registerObserver(Observer observer) {
observers.add(observer);
}
public void removeObserver(Observer observer) {
observers.remove(observer);
}
public void notifyObservers() {
for (Observer observer : observers) {
observer.update();
}
}
}
// 抽象观察者
public interface Observer {
void update();
}
// 具体观察者1
public class ConcreteObserver1 implements Observer {
private Subject subject;
public ConcreteObserver1(Subject subject) {
this.subject = subject;
subject.registerObserver(this);
}
public void update() {
System.out.println("ConcreteObserver1: " + subject.getState());
}
}
// 具体观察者2
public class ConcreteObserver2 implements Observer {
private Subject subject;
public ConcreteObserver2(Subject subject) {
this.subject = subject;
subject.registerObserver(this);
}
public void update() {
System.out.println("ConcreteObserver2: " + subject.getState());
}
}
// 客户端
public class Client {
public static void main(String[] args) {
ConcreteSubject subject = new ConcreteSubject();
new ConcreteObserver1(subject);
new ConcreteObserver2(subject);
subject.setState("State 1");
System.out.println();
subject.setState("State 2");
}
}状态模式允许一个对象在其内部状态改变时改变它的行为。对象看起来似乎修改了它的类。
// 抽象状态
public interface State {
void handle(Context context);
}
// 具体状态1
public class ConcreteState1 implements State {
public void handle(Context context) {
System.out.println("ConcreteState1");
context.setState(new ConcreteState2());
}
}
// 具体状态2
public class ConcreteState2 implements State {
public void handle(Context context) {
System.out.println("ConcreteState2");
context.setState(new ConcreteState1());
}
}
// 环境类
public class Context {
private State state;
public Context() {
state = new ConcreteState1();
}
public void setState(State state) {
this.state = state;
}
public State getState() {
return state;
}
public void request() {
state.handle(this);
}
}
// 客户端
public class Client {
public static void main(String[] args) {
Context context = new Context();
context.request();
context.request();
context.request();
context.request();
}
}策略模式定义了一系列的算法,并将每一个算法封装起来,而且使它们还可以相互替换。策略模式让算法独立于使用它的客户而独立变化。
// 抽象策略
public interface Strategy {
int doOperation(int num1, int num2);
}
// 具体策略1
public class OperationAdd implements Strategy {
public int doOperation(int num1, int num2) {
return num1 + num2;
}
}
// 具体策略2
public class OperationSubtract implements Strategy {
public int doOperation(int num1, int num2) {
return num1 - num2;
}
}
// 具体策略3
public class OperationMultiply implements Strategy {
public int doOperation(int num1, int num2) {
return num1 * num2;
}
}
// 环境类
public class Context {
private Strategy strategy;
public Context(Strategy strategy) {
this.strategy = strategy;
}
public int executeStrategy(int num1, int num2) {
return strategy.doOperation(num1, num2);
}
}
// 客户端
public class Client {
public static void main(String[] args) {
Context context = new Context(new OperationAdd());
System.out.println("10 + 5 = " + context.executeStrategy(10, 5));
context = new Context(new OperationSubtract());
System.out.println("10 - 5 = " + context.executeStrategy(10, 5));
context = new Context(new OperationMultiply());
System.out.println("10 * 5 = " + context.executeStrategy(10, 5));
}
}浅拷贝是指创建一个新对象,这个对象有着原始对象属性值的一份精确拷贝。如果属性是基本类型,拷贝的就是基本类型的值;如果属性是引用类型,拷贝的就是内存地址 ,所以如果其中一个对象改变了这个地址,就会影响到另一个对象。
class Address implements Cloneable {
String city;
public Address(String city) {
this.city = city;
}
@Override
protected Object clone() throws CloneNotSupportedException {
return super.clone();
}
}
class Person implements Cloneable {
String name;
int age;
Address address;
public Person(String name, int age, Address address) {
this.name = name;
this.age = age;
this.address = address;
}
@Override
protected Object clone() throws CloneNotSupportedException {
return super.clone(); // 浅拷贝
}
public static void main(String[] args) throws CloneNotSupportedException {
Address address = new Address("New York");
Person p1 = new Person("Alice", 25, address);
Person p2 = (Person) p1.clone(); // 浅拷贝
System.out.println(p1.address.city == p2.address.city); // true,引用相同
}
}深拷贝是指创建一个新对象,并且递归地复制所有字段,包括引用类型字段所引用的对象,使得新对象与原对象完全独立。
class Address implements Cloneable {
String city;
public Address(String city) {
this.city = city;
}
@Override
protected Object clone() throws CloneNotSupportedException {
return super.clone();
}
}
class Person implements Cloneable {
String name;
int age;
Address address;
public Person(String name, int age, Address address) {
this.name = name;
this.age = age;
this.address = address;
}
@Override
protected Object clone() throws CloneNotSupportedException {
Person clone = (Person) super.clone();
clone.address = (Address) address.clone(); // 深拷贝,创建新的Address
return clone;
}
public static void main(String[] args) throws CloneNotSupportedException {
Address address = new Address("New York");
Person p1 = new Person("Alice", 25, address);
Person p2 = (Person) p1.clone(); // 深拷贝
System.out.println(p1.address.city == p2.address.city); // false,引用不同
}
}深拷贝的实现方式:
- 重写clone()方法,对引用类型的属性也进行clone()
- 使用序列化进行深拷贝(通过将对象序列化再反序列化来实现深拷贝)
列表(List) 是 Java 中常用的数据结构之一,用于存储有序的元素集合。List 接口有多个实现类,如 ArrayList 和 LinkedList。
- 基于动态数组实现,支持快速的随机访问。
- 插入和删除操作代价较高,尤其是在列表中间进行操作时,因为需要移动元素。
import java.util.ArrayList;
import java.util.List;
public class Main {
public static void main(String[] args) {
// 创建一个ArrayList
List<String> list = new ArrayList<>();
// 添加元素
list.add("Alice");
list.add("Bob");
list.add("Charlie");
// 获取元素
System.out.println(list.get(0)); // 输出:Alice
// 修改元素
list.set(0, "Alex");
// 删除元素
list.remove(1);
// 遍历列表
for (String name : list) {
System.out.println(name);
}
// 获取列表大小
System.out.println("Size: " + list.size());
}
}哈希表(HashMap) 是 Java 中常用的数据结构之一,用于存储键值对。HashMap 基于哈希表实现,提供了快速的插入、删除和查找操作。
import java.util.HashMap;
import java.util.Map;
public class Main {
public static void main(String[] args) {
// 创建一个HashMap
Map<String, Integer> map = new HashMap<>();
// 添加键值对
map.put("Alice", 25);
map.put("Bob", 30);
map.put("Charlie", 35);
// 获取值
System.out.println(map.get("Alice")); // 输出:25
// 修改值
map.put("Alice", 26);
// 删除键值对
map.remove("Bob");
// 遍历HashMap
for (Map.Entry<String, Integer> entry : map.entrySet()) {
System.out.println(entry.getKey() + ": " + entry.getValue());
}
// 检查键是否存在
System.out.println(map.containsKey("Charlie")); // 输出:true
// 检查值是否存在
System.out.println(map.containsValue(35)); // 输出:true
// 获取HashMap大小
System.out.println("Size: " + map.size());
}
}abstract:用于声明抽象类或抽象方法,不能直接实例化。
abstract class Animal {
public abstract void sound();
}interface:用于声明接口,类通过 implements 实现接口中的方法。
interface Animal {
void sound();
}extends:用于声明类的继承关系,子类继承父类的属性和方法。
class Dog extends Animal {
void sound() {
System.out.println("Bark");
}
}implements:用于声明类实现接口,一个类可以实现一个或多个接口。
class Dog implements Animal {
public void sound() {
System.out.println("Bark");
}
}public、protected、private:用于声明类、方法或变量的访问权限。
public class Animal {
public String name;
protected int age;
private String color;
}static:用于声明类变量或类方法,不依赖对象实例访问。
class Counter {
public static int count = 0;
public static void increment() {
count++;
}
}final:用于声明常量、不可继承的类、不可重写的方法。
final class MathUtils {
public static final double PI = 3.14159;
public final int add(int a, int b) {
return a + b;
}
}1. 请举例说明你对创建型设计模式的理解
创建型模式分为5种:简单工厂模式、工厂模式、抽象工厂模式、原型模式、单例模式。
创建型模式关注对象的创建过程,通过将对象的创建和使用分离,来提高系统的灵活性和可复用性。
例如,工厂方法模式定义了一个创建对象的接口,但由子类决定要实例化的类是哪一个。工厂方法让类把实例化推迟到子类。
2. 请简述什么是适配器模式,并举例说明
适配器模式将一个类的接口转换成客户希望的另外一个接口。适配器模式使得原本由于接口不兼容而不能一起工作的那些类可以一起工作。
例如,假设我们有一个已存在的LegacyPrinter类,它有一个printDocument()方法。现在我们需要使用一个新的ModernPrinter接口,该接口有一个print()方法。我们可以创建一个适配器类PrinterAdapter,它实现ModernPrinter接口,并在内部使用LegacyPrinter的printDocument()方法。
1. 请简述什么是装饰器模式,并举例说明
装饰器模式动态地给一个对象添加一些额外的职责。就增加功能来说,装饰器模式相比生成子类更为灵活。
例如,假设我们有一个Shape接口和一个Circle类实现了这个接口。如果我们想给圆形添加颜色,我们可以创建一个ColorDecorator类,它也实现Shape接口,并在内部持有一个Shape对象的引用。ColorDecorator的draw()方法会先调用持有的Shape对象的draw()方法,然后再添加颜色相关的操作。
2. 请简述什么是观察者模式,并举例说明
观察者模式定义了一种一对多的依赖关系,让多个观察者对象同时监听某一个主题对象。这个主题对象在状态发生变化时,会通知所有观察者对象,使它们能够自动更新自己。
例如,假设我们有一个WeatherData类作为主题,它会定期获取天气数据。还有多个观察者类,如CurrentConditionsDisplay、StatisticsDisplay等,它们会显示天气数据。当WeatherData获取到新的天气数据时,会通知所有观察者更新它们的显示。
1. 请简述什么是策略模式,并举例说明
策略模式定义了一系列的算法,并将每一个算法封装起来,而且使它们还可以相互替换。策略模式让算法独立于使用它的客户而独立变化。
例如,假设我们有一个Sort类,它需要对数组进行排序。我们可以定义一个SortStrategy接口,然后创建多个实现类,如BubbleSort、QuickSort、MergeSort等。Sort类会持有一个SortStrategy对象的引用,并在需要排序时调用该对象的sort()方法。这样,我们可以在运行时动态地改变排序算法。
2. 请简述什么是单例模式,并举例说明
单例模式确保一个类只有一个实例,并提供一个访问它的全局访问点。
例如,假设我们有一个Logger类,它负责记录系统日志。为了确保所有的日志都写入同一个日志文件,我们可以将Logger设计为单例模式。这样,无论在系统的哪个地方获取Logger实例,都只会得到同一个对象。
1. 请简述什么是工厂方法模式,并举例说明
工厂方法模式定义了一个创建对象的接口,但由子类决定要实例化的类是哪一个。工厂方法让类把实例化推迟到子类。
例如,假设我们有一个Document类和一个DocumentFactory接口。DocumentFactory有一个createDocument()方法。然后我们可以创建WordDocumentFactory、PdfDocumentFactory等具体类,它们分别创建WordDocument和PdfDocument对象。客户端代码只需要知道DocumentFactory接口,而不需要知道具体的文档类型。
2. 请简述什么是责任链模式,并举例说明
责任链模式为请求创建了一个接收者对象的链。这种模式给予请求的类型,对请求的发送者和接收者进行解耦。
例如,假设我们有一个请假审批系统。员工请假需要经过组长、部门经理、总经理的审批。我们可以创建一个Approver抽象类,然后创建TeamLeader、DepartmentManager、GeneralManager等具体类。每个审批者都有一个nextApprover属性,指向链中的下一个审批者。当收到请假申请时,审批者会判断自己是否有权限审批,如果有就审批,否则就将申请传递给下一个审批者。
1. 请简述什么是原型模式,并举例说明
原型模式用原型实例指定创建对象的种类,并且通过拷贝这些原型创建新的对象。
例如,假设我们有一个Report类,它包含很多数据和复杂的初始化过程。如果我们需要创建多个相似的报告对象,我们可以使用原型模式。我们让Report类实现Cloneable接口,并重写clone()方法。然后,我们可以创建一个原型报告对象,通过调用clone()方法来快速创建新的报告对象,而不需要重新初始化。
2. 请简述什么是命令模式,并举例说明
命令模式将一个请求封装为一个对象,从而使你可用不同的请求对客户进行参数化;对请求排队或记录请求日志,以及支持可撤销的操作。
例如,假设我们有一个遥控器,可以控制各种家电。我们可以定义一个Command接口,它有一个execute()方法。然后创建多个具体的命令类,如LightOnCommand、LightOffCommand、TVOnCommand等。遥控器上的每个按钮都关联一个Command对象。当按下按钮时,遥控器会调用对应的Command对象的execute()方法。这样,我们可以很容易地为遥控器添加新的功能,或者改变按钮的功能。
1. 请简述什么是状态模式,并举例说明
状态模式允许一个对象在其内部状态改变时改变它的行为。对象看起来似乎修改了它的类。
例如,假设我们有一个TrafficLight类,它有红、黄、绿三种状态。在不同的状态下,交通灯的行为是不同的(红灯停止,绿灯通行,黄灯准备停止)。我们可以定义一个State接口,然后创建RedState、YellowState、GreenState等具体类。TrafficLight类会持有一个State对象的引用,并在需要改变状态时调用该对象的方法。
2. 请简述什么是组合模式,并举例说明
组合模式将对象组合成树形结构以表示"部分-整体"的层次结构。组合模式使得用户对单个对象和组合对象的使用具有一致性。
例如,假设我们有一个文件系统,包含文件和文件夹。文件夹可以包含文件和其他文件夹。我们可以定义一个FileSystemComponent抽象类,然后创建File和Folder两个具体类。Folder类可以包含一个FileSystemComponent对象的列表。这样,我们可以统一地处理文件和文件夹,例如计算大小、显示内容等。
1. GOF设计模式有几种类型,分别包括那些模式
23种,分别是创建型模式(5种)、结构型模式(7种)、行为型模式(11种)
创建型模式:工厂方法模式、抽象工厂模式、单例模式、建造者模式、原型模式
结构型模式:适配器模式、桥接模式、组合模式、装饰模式、外观模式、享元模式、代理模式
行为型模式:责任链模式、命令模式、解释器模式、迭代器模式、中介者模式、备忘录模式、观察者模式、状态模式、策略模式、模板方法模式、访问者模式
2. 请简述什么是依赖倒置原则,并举例说明
依赖倒置原则是指:高层模块不应该依赖低层模块,两者都应该依赖抽象;抽象不应该依赖细节,细节应该依赖抽象。
例如,假设我们有一个UserService类,它需要访问数据库。如果UserService直接依赖于MySQLUserRepository类,那么当我们需要切换到其他数据库时,就需要修改UserService的代码。正确的做法是定义一个UserRepository接口,让MySQLUserRepository实现这个接口。然后,UserService依赖于UserRepository接口,而不是具体的实现类。这样,当需要切换数据库时,只需要创建一个新的UserRepository实现类,而不需要修改UserService的代码。
3. 请简述什么是开闭原则,并举例说明
开闭原则是指:一个软件实体应当对扩展开放,对修改关闭。也就是说,软件实体应该在不修改原有代码的情况下,通过扩展来实现新的功能。
例如,假设我们有一个Shape接口和一个AreaCalculator类,它可以计算不同形状的面积。如果我们需要添加一种新的形状,如三角形,我们不应该修改现有的AreaCalculator类,而是应该创建一个新的Triangle类实现Shape接口,然后创建一个新的TriangleAreaCalculator类来计算三角形的面积。或者,我们可以让AreaCalculator类依赖于Shape接口,然后在Shape接口中添加一个calculateArea()方法。这样,当添加新的形状时,只需要实现Shape接口,而不需要修改AreaCalculator类。
4. 请简述什么是里氏代换原则,并举例说明
里氏代换原则是指:子类对象必须能够替换掉父类对象,并且程序的行为不会发生变化。
例如,假设我们有一个Rectangle类,它有width和height两个属性,以及一个calculateArea()方法。然后我们创建一个Square类继承自Rectangle。但是,正方形的宽和高必须相等。如果我们在Square类中重写setWidth()和setHeight()方法,使得设置宽时也会设置高,反之亦然,那么Square对象就不能完全替换Rectangle对象。因为当我们将一个Square对象赋值给Rectangle变量,并分别设置宽和高时,得到的结果可能不是我们期望的。
5. 请简述什么是单一职责原则,并举例说明
单一职责原则是指:一个类应该只有一个引起它变化的原因。也就是说,一个类应该只负责一项职责。
例如,假设我们有一个User类,它负责用户信息的管理和用户的登录验证。这违反了单一职责原则,因为用户信息管理和登录验证是两个不同的职责。正确的做法是创建两个类:User类负责用户信息管理,UserAuthenticator类负责登录验证。
6. 请简述什么是接口隔离原则,并举例说明
接口隔离原则是指:客户端不应该依赖它不需要的接口。也就是说,一个接口应该只包含客户端需要的方法。
例如,假设我们有一个Shape接口,它包含draw()、resize()和serialize()三个方法。如果有一个Circle类只需要draw()和resize()方法,而不需要serialize()方法,那么这违反了接口隔离原则。正确的做法是将Shape接口拆分为Drawable、Resizable和Serializable三个接口。Circle类可以实现Drawable和Resizable接口,而不需要实现Serializable接口。
7. 请简述什么是迪米特原则,并举例说明
迪米特原则是指:一个对象应该对其他对象有最少的了解。也就是说,一个对象应该尽量少地与其他对象发生相互作用。
例如,假设我们有一个A类,它需要调用C类的doSomething()方法。如果A类直接通过B类来调用C类的方法,如a.getB().getC().doSomething(),那么A类需要了解B类和C类的内部结构,这违反了迪米特原则。正确的做法是让A类直接调用C类的方法,或者让B类提供一个方法来调用C类的方法,而不需要A类了解C类的存在。
8. 请简述什么是组合/聚合复用原则,并举例说明
组合/聚合复用原则是指:尽量使用组合或聚合的方式,而不是继承的方式来实现复用。
例如,假设我们有一个Car类和一个Engine类。如果让Car类继承Engine类,这是不合适的,因为汽车"有"发动机,而不是"是"发动机。正确的做法是在Car类中包含一个Engine类型的成员变量(组合关系)。这样,Car类可以使用Engine类的功能,而不需要继承它。