设计模式是编码过程中非常值得学习和使用的编码技巧,常说代码搬运工都是喜欢一行代码写到底,不然就是ifelse写满屏,虽然不能完全解决这些问题,但是使用设计模式能很好帮助我们管理代码和养成良好的习惯
- 六大基本原则
单一责任原则:单一责任原则最容易理解,一个类或者一个方法只负责干一样事,听起来很简单,但实际上编码的过程中很容易忽略这点,我们常说的在屎山上堆屎很多时候都忽略了单一责任,最好的方式:(1)不同功能或者不同性质的方法不要放在同一个类里;(2)单个类要放相关性高的代码,不相干的就抽出去;
里氏替换原则:所有基类使用的地方,都用子类代替,里氏替换原则跟类的集成有关,给大家举个例子,我们一般使用Map<String, Object>,会new HashMap<>,这样在使用到map的地方,都用了父类的方法,实际上使用都用了HashMap的实现方法,我们讨论一下这样做有什么好处呢?刚刚的例子是使用接口方式的实现,这样可以更好发挥Map不同实现的方式,如果定义的是一个父类方法,里氏替换则不允许子类去修改父类的方法,这样很容易在复杂的多态环境中造成方法内部错误和紊乱,而这种情况下建议采用接口的方式,不同实现则用自己的具体的实现,互不干扰;
依赖倒置原则:简单来说就是,高层不能依赖底层,抽象或者接口,不依赖具体的实现类,主要是为了减少耦合,这也是很多系统在快速迭代中容易忽略的问题
接口隔离原则:建立接口必须单一责任,不能建太大的范围,职责分明,但是不能啥都细分,需要做一个折中考虑,在职责分明的情况下,尽量不能让类太多
迪米特原则:这个原则我们听得最少,其实很简单,就是一个类引用另一个类的时候不需要知道它太多,这也是为了降低耦合,知道的越少越好,需要什么就拿什么,细节不需要知道
开闭原则:我们听得最多的就是这个,开放拓展,关闭修改,这也是我们定义一个类最佳的使用方案,当然在不同情况下也是视情况可定
- 23种设计模式
(1)行为型:
策略模式:
首先,我们定义一个抽象策略类 PromotionStrategy
,它有一个抽象方法 doPromotion
,用于执行促销活动。
public abstract class PromotionStrategy {
public abstract void doPromotion();
}
然后,我们可以定义具体的促销策略类,比如 CouponStrategy
、CashbackStrategy
和 GroupbuyStrategy
,它们分别实现了抽象策略类,实现了 doPromotion
方法。
public class CouponStrategy extends PromotionStrategy {
public void doPromotion() {
System.out.println("使用优惠券促销活动");
}
}
public class CashbackStrategy extends PromotionStrategy {
public void doPromotion() {
System.out.println("使用返现促销活动");
}
}
public class GroupbuyStrategy extends PromotionStrategy {
public void doPromotion() {
System.out.println("使用团购促销活动");
}
}
最后,我们可以定义一个上下文类 PromotionContext
,它持有一个策略对象,并通过调用策略对象的方法来执行相应的促销活动。
public class PromotionContext {
private PromotionStrategy strategy;
public PromotionContext(PromotionStrategy strategy) {
this.strategy = strategy;
}
public void executePromotion() {
strategy.doPromotion();
}
}
通过上述的设计,我们可以根据需要选择不同的促销策略,并将其传递给上下文对象,然后调用上下文对象的方法来执行相应的促销活动。这样,我们可以方便地扩展和修改不同的促销策略,而不影响其他部分的代码。
public class Main {
public static void main(String[] args) {
// 使用优惠券促销活动
PromotionContext context1 = new PromotionContext(new CouponStrategy());
context1.executePromotion();
// 使用返现促销活动
PromotionContext context2 = new PromotionContext(new CashbackStrategy());
context2.executePromotion();
// 使用团购促销活动
PromotionContext context3 = new PromotionContext(new GroupbuyStrategy());
context3.executePromotion();
}
}
输出结果
使用优惠券促销活动
使用返现促销活动
使用团购促销活动
通过策略模式,我们可以灵活地切换和组合不同的促销策略,从而实现更加可扩展和可维护的代码结构。
状态模式:
状态模式允许对象在内部状态改变时改变其行为。状态模式将对象的行为封装在不同的状态类中,并通过上下文类来管理状态之间的切换。
下面举一个简单的例子来说明状态模式的应用。
假设我们有一个电梯系统,电梯有四种状态:停止状态、运行状态、开门状态和关门状态。不同状态下电梯的行为是不同的,我们可以使用状态模式来实现电梯系统。
首先,我们定义一个抽象状态类 ElevatorState
,它有两个抽象方法 open
和 close
,分别用于处理开门和关门的行为。
public abstract class ElevatorState {
public abstract void open();
public abstract void close();
}
然后,我们可以定义具体的状态类,分别是 StoppedState
、RunningState
、DoorOpenedState
和 DoorClosedState
,它们分别实现了抽象状态类,实现了 open
和 close
方法。
public class StoppedState extends ElevatorState {
public void open() {
System.out.println("电梯门已打开");
}
public void close() {
System.out.println("电梯门已关闭");
}
}
public class RunningState extends ElevatorState {
public void open() {
// do nothing
}
public void close() {
// do nothing
}
}
public class DoorOpenedState extends ElevatorState {
public void open() {
// do nothing
}
public void close() {
System.out.println("电梯门已关闭");
}
}
public class DoorClosedState extends ElevatorState {
public void open() {
System.out.println("电梯门已打开");
}
public void close() {
// do nothing
}
}
最后,我们定义一个上下文类 ElevatorContext
,它持有一个状态对象,并通过调用状态对象的方法来执行相应的行为。
public class ElevatorContext {
private ElevatorState state;
public ElevatorContext() {
// 默认为停止状态
this.state = new StoppedState();
}
public void setState(ElevatorState state) {
this.state = state;
}
public void open() {
state.open();
}
public void close() {
state.close();
}
}
通过上述的设计,我们可以在上下文对象中管理电梯的状态,并根据需要切换不同的状态。当需要执行某个行为时,只需调用上下文对象的相应方法,它会自动委托给当前状态对象处理。
public class Main {
public static void main(String[] args) {
ElevatorContext context = new ElevatorContext();
// 开门
context.open();
// 切换状态为运行
context.setState(new RunningState());
// 关门
context.close();
// 切换状态为停止
context.setState(new StoppedState());
// 开门
context.open();
}
}
输出结果:
电梯门已打开
电梯门已关闭
电梯门已打开
通过状态模式,我们可以方便地扩展和修改电梯系统的不同状态和行为,而不需要修改其他部分的代码。状态模式使得对象的行为可以根据内部状态的改变而改变,从而实现更加灵活和可维护的代码结构。
观察者模式:
观察者模式定义了一种一对多的依赖关系,使得多个观察者对象可以同时监听并收到被观察者对象的通知。
下面举一个简单的例子来说明观察者模式的应用。
假设我们有一个新闻发布系统,用户可以订阅不同的新闻主题,并在有新闻发布时接收到相应的通知。我们可以使用观察者模式来实现新闻发布系统。
首先,我们定义一个抽象观察者类 Observer
,它有一个抽象方法 update
,用于接收被观察者的通知。
public abstract class Observer {
public abstract void update(String news);
}
然后,我们定义具体的观察者类 User
,它实现了抽象观察者类,实现了 update
方法。
public class User extends Observer {
private String name;
public User(String name) {
this.name = name;
}
public void update(String news) {
System.out.println(name + "收到新闻:" + news);
}
}
接下来,我们定义一个抽象被观察者类 Subject
,它有两个方法 attach
和 detach
,用于添加和移除观察者;以及一个抽象方法 notifyObservers
,用于通知所有观察者。
import java.util.ArrayList;
import java.util.List;
public abstract class Subject {
private List<Observer> observers = new ArrayList<>();
public void attach(Observer observer) {
observers.add(observer);
}
public void detach(Observer observer) {
observers.remove(observer);
}
public void notifyObservers(String news) {
for (Observer observer : observers) {
observer.update(news);
}
}
}
最后,我们定义一个具体的被观察者类 NewsPublisher
,它继承了抽象被观察者类,可以添加、移除和通知观察者。
public class NewsPublisher extends Subject {
public void publishNews(String news) {
notifyObservers(news);
}
}
通过上述的设计,我们可以在发布新闻时,调用被观察者对象的 notifyObservers
方法,从而通知所有观察者收到新闻。
public class Main {
public static void main(String[] args) {
NewsPublisher publisher = new NewsPublisher();
// 创建观察者对象
Observer user1 = new User("User1");
Observer user2 = new User("User2");
// 添加观察者
publisher.attach(user1);
publisher.attach(user2);
// 发布新闻
publisher.publishNews("今天是个好日子!");
// 移除观察者
publisher.detach(user2);
// 再次发布新闻
publisher.publishNews("明天要下雨了。");
}
}
输出结果:
User1收到新闻:今天是个好日子!
User2收到新闻:今天是个好日子!
User1收到新闻:明天要下雨了。
通过观察者模式,我们可以实现对象之间的松耦合关系,被观察者对象和观察者对象之间互不依赖,从而使系统更加灵活和可扩展。观察者模式常用于事件驱动的系统,如GUI界面、消息中间件等。
中介者模式:
中介者模式通过提供一个中介对象来封装一系列对象之间的交互,从而使对象之间的通信变得简单和松散耦合。
下面举一个简单的例子来说明中介者模式的应用。
假设我们有一个聊天室系统,多个用户可以在聊天室中相互发送消息。为了实现用户之间的通信,我们可以使用中介者模式。
首先,我们定义一个抽象中介者类 ChatRoom
,它有一个抽象方法 sendMessage
,用于处理用户发送的消息。
public abstract class ChatRoom {
public abstract void sendMessage(String message, User user);
}
然后,我们定义具体的中介者类 ConcreteChatRoom
,它维护一个用户列表,并实现了抽象中介者类,实现了 sendMessage
方法。
import java.util.ArrayList;
import java.util.List;
public class ConcreteChatRoom extends ChatRoom {
private List<User> users = new ArrayList<>();
public void addUser(User user) {
users.add(user);
}
public void sendMessage(String message, User sender) {
for (User user : users) {
if (user != sender) {
user.receiveMessage(message);
}
}
}
}
接下来,我们定义一个抽象同事类 User
,它持有一个中介者对象,并有一个抽象方法 sendMessage
,用于发送消息;以及一个普通方法 receiveMessage
,用于接收消息。
public abstract class User {
protected ChatRoom chatRoom;
public User(ChatRoom chatRoom) {
this.chatRoom = chatRoom;
}
public abstract void sendMessage(String message);
public void receiveMessage(String message) {
System.out.println("收到消息:" + message);
}
}
然后,我们定义具体的同事类 ConcreteUser
,它继承了抽象同事类,实现了 sendMessage
方法。
public class ConcreteUser extends User {
private String name;
public ConcreteUser(String name, ChatRoom chatRoom) {
super(chatRoom);
this.name = name;
}
public void sendMessage(String message) {
chatRoom.sendMessage(message, this);
}
}
通过上述的设计,用户可以通过调用自己的 sendMessage
方法发送消息,消息会被中介者对象 ChatRoom
处理,并通过调用其他用户的 receiveMessage
方法进行消息的转发。
public class Main {
public static void main(String[] args) {
ChatRoom chatRoom = new ConcreteChatRoom();
// 创建用户对象
User user1 = new ConcreteUser("User1", chatRoom);
User user2 = new ConcreteUser("User2", chatRoom);
User user3 = new ConcreteUser("User3", chatRoom);
// 添加用户到聊天室
((ConcreteChatRoom) chatRoom).addUser(user1);
((ConcreteChatRoom) chatRoom).addUser(user2);
((ConcreteChatRoom) chatRoom).addUser(user3);
// 用户发送消息
user1.sendMessage("Hello, User2 and User3!");
user2.sendMessage("Hi, User1!");
user3.sendMessage("Nice to meet you all!");
}
}
输出结果:
收到消息:Hello, User2 and User3!
收到消息:Hi, User1!
收到消息:Nice to meet you all!
通过中介者模式,我们可以将对象之间的通信逻辑集中到中介者对象中,从而简化对象之间的交互。中介者模式使得对象之间的通信变得松散耦合,从而提高了代码的可维护性和扩展性。中介者模式常用于复杂的交互场景,如聊天室、飞机控制系统等。
访问者模式:
访问者模式将数据结构与对数据的操作分离,使得可以在不修改数据结构的情况下定义新的操作。
下面举一个简单的例子来说明访问者模式的应用。
假设我们有一个图形库,其中包含了不同类型的图形对象,如圆形、矩形等。我们想要实现一个功能,即计算所有图形对象的总面积和总周长。我们可以使用访问者模式来实现这个功能。
首先,我们定义一个抽象访问者类 Visitor
,它有多个抽象方法,用于对不同类型的图形对象进行访问。
public abstract class Visitor {
public abstract void visit(Circle circle);
public abstract void visit(Rectangle rectangle);
}
然后,我们定义具体的访问者类 AreaVisitor
,它继承了抽象访问者类,实现了对图形对象计算面积的方法;以及 PerimeterVisitor
,它实现了对图形对象计算周长的方法。
public class AreaVisitor extends Visitor {
private double totalArea = 0;
public void visit(Circle circle) {
totalArea += Math.PI * Math.pow(circle.getRadius(), 2);
}
public void visit(Rectangle rectangle) {
totalArea += rectangle.getLength() * rectangle.getWidth();
}
public double getTotalArea() {
return totalArea;
}
}
public class PerimeterVisitor extends Visitor {
private double totalPerimeter = 0;
public void visit(Circle circle) {
totalPerimeter += 2 * Math.PI * circle.getRadius();
}
public void visit(Rectangle rectangle) {
totalPerimeter += 2 * (rectangle.getLength() + rectangle.getWidth());
}
public double getTotalPerimeter() {
return totalPerimeter;
}
}
接下来,我们定义一个抽象元素类 Shape
,它有一个抽象方法 accept
,用于接受访问者的访问。
public abstract class Shape {
public abstract void accept(Visitor visitor);
}
然后,我们定义具体的元素类 Circle
和 Rectangle
,它们继承了抽象元素类,实现了 accept
方法。
public class Circle extends Shape {
private double radius;
public Circle(double radius) {
this.radius = radius;
}
public double getRadius() {
return radius;
}
public void accept(Visitor visitor) {
visitor.visit(this);
}
}
public class Rectangle extends Shape {
private double length;
private double width;
public Rectangle(double length, double width) {
this.length = length;
this.width = width;
}
public double getLength() {
return length;
}
public double getWidth() {
return width;
}
public void accept(Visitor visitor) {
visitor.visit(this);
}
}
通过上述的设计,我们可以在访问者对象中定义不同的操作,而不需要修改图形对象的结构。访问者可以通过调用图形对象的 accept
方法来访问图形对象,从而进行相应的操作。
public class Main {
public static void main(String[] args) {
List<Shape> shapes = new ArrayList<>();
shapes.add(new Circle(5));
shapes.add(new Rectangle(3, 4));
shapes.add(new Circle(7));
// 创建访问者对象
Visitor areaVisitor = new AreaVisitor();
Visitor perimeterVisitor = new PerimeterVisitor();
// 计算总面积
for (Shape shape : shapes) {
shape.accept(areaVisitor);
}
double totalArea = ((AreaVisitor) areaVisitor).getTotalArea();
System.out.println("总面积:" + totalArea);
// 计算总周长
for (Shape shape : shapes) {
shape.accept(perimeterVisitor);
}
double totalPerimeter = ((PerimeterVisitor) perimeterVisitor).getTotalPerimeter();
System.out.println("总周长:" + totalPerimeter);
}
}
输出结果:
总面积:153.93804002589985
总周长:72.84955592153876
通过访问者模式,我们可以将数据结构与对数据的操作分离,使得可以在不修改数据结构的情况下定义新的操作。访问者模式适用于数据结构相对稳定,但经常需要定义新的操作的场景。它可以提高代码的可扩展性和灵活性,但也会增加代码的复杂度。
迭代器模式:
迭代器模式提供一种顺序访问聚合对象中各个元素的方法,而不需要暴露聚合对象的内部表示。
下面举一个简单的例子来说明迭代器模式的应用。
假设我们有一个商店,其中有多种商品,我们想要实现一个功能,即遍历商店中的所有商品。我们可以使用迭代器模式来实现这个功能。
首先,我们定义一个抽象迭代器类 Iterator
,它有两个抽象方法 hasNext
和 next
,用于判断是否还有下一个元素和获取下一个元素。
public interface Iterator {
public boolean hasNext();
public Object next();
}
然后,我们定义一个抽象聚合类 Aggregate
,它有一个抽象方法 createIterator
,用于创建一个迭代器。
public interface Aggregate {
public Iterator createIterator();
}
接下来,我们定义具体的迭代器类 ProductIterator
,它实现了抽象迭代器类,用于遍历商店中的商品。
public class ProductIterator implements Iterator {
private Product[] products;
private int position = 0;
public ProductIterator(Product[] products) {
this.products = products;
}
public boolean hasNext() {
if (position < products.length) {
return true;
} else {
return false;
}
}
public Object next() {
Product product = products[position];
position++;
return product;
}
}
然后,我们定义具体的聚合类 Shop
,它实现了抽象聚合类,用于创建一个迭代器,以便遍历商店中的商品。
public class Shop implements Aggregate {
private Product[] products;
public Shop(Product[] products) {
this.products = products;
}
public Iterator createIterator() {
return new ProductIterator(products);
}
}
最后,我们定义一个商品类 Product
,它包含了商品的名称和价格。
public class Product {
private String name;
private double price;
public Product(String name, double price) {
this.name = name;
this.price = price;
}
public String getName() {
return name;
}
public double getPrice() {
return price;
}
}
通过上述的设计,我们可以通过调用聚合对象的 createIterator
方法来创建一个迭代器,从而遍历商店中的所有商品。
public class Main {
public static void main(String[] args) {
Product[] products = new Product[3];
products[0] = new Product("Apple", 5.0);
products[1] = new Product("Banana", 3.0);
products[2] = new Product("Orange", 2.5);
// 创建聚合对象
Shop shop = new Shop(products);
// 创建迭代器
Iterator iterator = shop.createIterator();
// 遍历商品
while (iterator.hasNext()) {
Product product = (Product) iterator.next();
System.out.println("商品名称:" + product.getName() + ",价格:" + product.getPrice());
}
}
}
输出结果:
商品名称:Apple,价格:5.0
商品名称:Banana,价格:3.0
商品名称:Orange,价格:2.5
通过迭代器模式,我们可以封装遍历聚合对象的方式,使得遍历过程与聚合对象的内部结构分离。迭代器模式提供了一种统一的访问方式,使得可以使用相同的代码来遍历不同类型的聚合对象。迭代器模式适用于需要遍历聚合对象的场景,如集合类、树形结构等。它可以提高代码的可扩展性和灵活性,同时也使得代码更加简洁和易于维护。
模板方法:
模板方法定义了一个算法的骨架,将一些步骤的具体实现延迟到子类中。模板方法模式的核心思想是将通用的算法步骤封装到一个抽象类中,而将具体的实现细节交给子类去实现。
下面举个例子来说明模板方法的应用场景和实现方式。
假设我们要设计一个游戏,其中包含一个游戏角色类,该类有一个公共的游戏流程,包括角色创建、角色选择、角色战斗等步骤。但是每个角色在具体实现这些步骤时可能有所不同,比如不同的角色可能有不同的创建方式、选择方式和战斗策略。
这时候可以使用模板方法模式来实现。我们可以创建一个抽象的角色类,其中定义了一个模板方法,表示游戏流程的骨架。然后在该抽象类中定义一些抽象的操作方法,表示各个步骤的具体实现。具体的角色类可以继承抽象角色类,实现这些抽象方法,完成自己的具体步骤。
// 抽象角色类
public abstract class GameCharacter {
// 模板方法,定义了游戏流程的骨架
public void playGame() {
createCharacter();
selectCharacter();
fight();
}
// 抽象方法,表示创建角色的具体实现
protected abstract void createCharacter();
// 抽象方法,表示选择角色的具体实现
protected abstract void selectCharacter();
// 抽象方法,表示战斗的具体实现
protected abstract void fight();
}
// 具体角色类1
public class CharacterA extends GameCharacter {
@Override
protected void createCharacter() {
System.out.println("角色A的创建方式");
}
@Override
protected void selectCharacter() {
System.out.println("角色A的选择方式");
}
@Override
protected void fight() {
System.out.println("角色A的战斗策略");
}
}
// 具体角色类2
public class CharacterB extends GameCharacter {
@Override
protected void createCharacter() {
System.out.println("角色B的创建方式");
}
@Override
protected void selectCharacter() {
System.out.println("角色B的选择方式");
}
@Override
protected void fight() {
System.out.println("角色B的战斗策略");
}
}
// 测试类
public class Test {
public static void main(String[] args) {
GameCharacter characterA = new CharacterA();
characterA.playGame();
GameCharacter characterB = new CharacterB();
characterB.playGame();
}
}
以上代码中,抽象角色类GameCharacter
定义了一个playGame()
方法作为模板方法,具体实现了游戏流程的骨架。CharacterA
和CharacterB
是具体的角色类,继承了抽象角色类,并实现了抽象方法来完成自己的具体实现。
运行测试类,输出结果如下:
角色A的创建方式
角色A的选择方式
角色A的战斗策略
角色B的创建方式
角色B的选择方式
角色B的战斗策略
可以看到,通过模板方法模式,我们可以在抽象类中定义通用的流程,而具体的实现细节则由子类来完成。这样可以在保持整体流程一致的同时,方便扩展和定制每个子类的具体实现。
备忘录模式:
备忘录模式用于捕获和存储一个对象的内部状态,以便在需要时可以恢复到之前的状态。备忘录模式的核心思想是将对象的状态保存在一个备忘录对象中,并在需要时可以将对象恢复到备忘录对象所保存的状态。
下面举个例子来说明备忘录模式的应用场景和实现方式。
假设我们正在开发一个文本编辑器,用户可以在编辑器中输入文本,并进行撤销和恢复操作。为了实现撤销和恢复功能,我们可以使用备忘录模式。
首先,我们需要创建一个备忘录类,用于保存文本编辑器的状态。该备忘录类应该有一个用于保存状态的方法和一个用于获取状态的方法。
public class EditorMemento {
private String text;
public EditorMemento(String text) {
this.text = text;
}
public String getText() {
return text;
}
}
然后,我们需要创建一个发起人类,即文本编辑器类。该类应该有一个用于保存当前状态的方法和一个用于恢复之前状态的方法。
public class TextEditor {
private String text;
public void setText(String text) {
this.text = text;
}
public String getText() {
return text;
}
public EditorMemento save() {
return new EditorMemento(text);
}
public void restore(EditorMemento memento) {
text = memento.getText();
}
}
最后,我们可以创建一个客户端类,用于测试备忘录模式的使用。
public class Client {
public static void main(String[] args) {
TextEditor editor = new TextEditor();
// 输入文本并保存状态
editor.setText("Hello");
EditorMemento memento1 = editor.save();
// 输入新的文本并保存状态
editor.setText("World");
EditorMemento memento2 = editor.save();
// 恢复到之前的状态
editor.restore(memento1);
System.out.println(editor.getText()); // 输出:Hello
// 再次恢复到之前的状态
editor.restore(memento2);
System.out.println(editor.getText()); // 输出:World
}
}
在上面的示例中,我们通过备忘录模式实现了文本编辑器的撤销和恢复功能。通过保存和恢复备忘录对象,我们可以将文本编辑器的状态恢复到之前的状态。
备忘录模式的优点是可以方便地保存和恢复对象的状态,使得对象的状态管理更加灵活。同时,备忘录模式也可以对状态进行封装,保护对象的状态不被外部访问和修改
命令模式:
命令模式将请求封装成一个对象,从而使得可以使用不同的请求来参数化其他对象,并且能够将请求操作的执行延迟或者排队,以便在需要时执行。命令模式的核心思想是将请求发送者与请求接收者解耦。
下面举个例子来说明命令模式的应用场景和实现方式。
假设我们正在开发一个遥控器应用程序,该应用程序有多个按钮,每个按钮对应一个命令,用户可以通过点击按钮来执行相应的命令。为了实现这个功能,我们可以使用命令模式。
首先,我们需要定义一个命令接口,该接口包含一个执行命令的方法。
public interface Command {
void execute();
}
然后,我们需要创建一些具体的命令类,实现命令接口,并在具体的命令类中实现具体的命令逻辑。
public class LightOnCommand implements Command {
private Light light;
public LightOnCommand(Light light) {
this.light = light;
}
public void execute() {
light.turnOn();
}
}
public class LightOffCommand implements Command {
private Light light;
public LightOffCommand(Light light) {
this.light = light;
}
public void execute() {
light.turnOff();
}
}
接下来,我们需要创建一个请求接收者类,该类包含了真正执行命令的方法。
public class Light {
public void turnOn() {
System.out.println("灯打开了");
}
public void turnOff() {
System.out.println("灯关闭了");
}
}
最后,我们可以创建一个请求发送者类,该类负责创建命令对象,并将命令对象与请求接收者对象进行绑定。
public class RemoteControl {
private Command[] onCommands;
private Command[] offCommands;
public RemoteControl() {
onCommands = new Command[2];
offCommands = new Command[2];
Light light = new Light();
onCommands[0] = new LightOnCommand(light);
offCommands[0] = new LightOffCommand(light);
}
public void onButtonPressed(int slot) {
onCommands[slot].execute();
}
public void offButtonPressed(int slot) {
offCommands[slot].execute();
}
}
最后,我们可以创建一个客户端类,用于测试命令模式的使用。
public class Client {
public static void main(String[] args) {
RemoteControl remoteControl = new RemoteControl();
// 按下开灯按钮
remoteControl.onButtonPressed(0);
// 按下关灯按钮
remoteControl.offButtonPressed(0);
}
}
在上面的示例中,我们使用命令模式实现了遥控器应用程序。通过将请求封装成命令对象,并将命令对象与请求接收者对象进行绑定,实现了请求发送者与请求接收者的解耦。用户可以通过点击按钮来执行相应的命令。
命令模式的优点是能够将请求发送者与请求接收者解耦,增加了系统的灵活性。同时,命令模式还可以支持命令的撤销和重做操作。
解释器模式:
解释器模式定义了一种语言的文法,并且定义了该语言中语句的解释器,用于解释和执行语句。解释器模式的核心思想是将语言的解释器封装成一个对象,通过使用该对象来解释和执行语句。
下面举个例子来说明解释器模式的应用场景和实现方式。
假设我们正在开发一个简单的数学表达式解析器,该解析器可以解析和计算简单的加法和减法表达式。为了实现这个功能,我们可以使用解释器模式。
首先,我们需要定义一个抽象的解释器接口,该接口包含一个解释方法,用于解释和执行语句。
public interface Expression {
int interpret();
}
然后,我们需要创建一些具体的解释器类,实现解释器接口,并在具体的解释器类中实现具体的解释逻辑。
public class NumberExpression implements Expression {
private int number;
public NumberExpression(int number) {
this.number = number;
}
public int interpret() {
return number;
}
}
public class AddExpression implements Expression {
private Expression leftExpression;
private Expression rightExpression;
public AddExpression(Expression leftExpression, Expression rightExpression) {
this.leftExpression = leftExpression;
this.rightExpression = rightExpression;
}
public int interpret() {
return leftExpression.interpret() + rightExpression.interpret();
}
}
public class SubtractExpression implements Expression {
private Expression leftExpression;
private Expression rightExpression;
public SubtractExpression(Expression leftExpression, Expression rightExpression) {
this.leftExpression = leftExpression;
this.rightExpression = rightExpression;
}
public int interpret() {
return leftExpression.interpret() - rightExpression.interpret();
}
}
接下来,我们可以创建一个客户端类,用于测试解释器模式的使用。
public class Client {
public static void main(String[] args) {
// 构建表达式:1 + 2 - 3
Expression expression = new SubtractExpression(
new AddExpression(new NumberExpression(1), new NumberExpression(2)),
new NumberExpression(3)
);
// 解释和计算表达式
int result = expression.interpret();
System.out.println("计算结果:" + result);
}
}
在上面的示例中,我们使用解释器模式实现了一个简单的数学表达式解析器。通过将表达式解析成一棵解释树,并使用解释器对象来解释和执行语句,我们可以实现对表达式的解析和计算。
解释器模式的优点是可以方便地扩展语言的文法,增加新的解释器类即可。同时,解释器模式也可以将解释器与语法规则分离,使得解释器的复杂性得到了解耦,提高了代码的可维护性和可扩展性。
责任链模式:
责任链模式允许多个对象依次处理同一个请求,直到其中一个对象能够处理请求为止。责任链模式的核心思想是将请求发送者和请求处理者解耦,使得请求处理者可以动态地组合成一个责任链,并依次处理请求。
下面举个例子来说明责任链模式的应用场景和实现方式。
假设我们正在开发一个请假审批系统,该系统有多个层级的审批人,每个审批人有不同的权限和审批级别。为了实现这个功能,我们可以使用责任链模式。
首先,我们需要定义一个抽象的审批人类,该类包含一个审批方法和一个设置下一个审批人的方法。
public abstract class Approver {
protected Approver nextApprover;
public void setNextApprover(Approver nextApprover) {
this.nextApprover = nextApprover;
}
public abstract void approveLeave(int days);
}
然后,我们需要创建一些具体的审批人类,继承自抽象的审批人类,并在具体的审批人类中实现具体的审批逻辑。
public class TeamLeader extends Approver {
public void approveLeave(int days) {
if (days <= 2) {
System.out.println("团队领导审批通过");
} else if (nextApprover != null) {
nextApprover.approveLeave(days);
}
}
}
public class DepartmentManager extends Approver {
public void approveLeave(int days) {
if (days <= 5) {
System.out.println("部门经理审批通过");
} else if (nextApprover != null) {
nextApprover.approveLeave(days);
}
}
}
public class CEO extends Approver {
public void approveLeave(int days) {
if (days <= 10) {
System.out.println("CEO审批通过");
} else {
System.out.println("请假天数太长,无法审批");
}
}
}
接下来,我们可以创建一个客户端类,用于测试责任链模式的使用。
public class Client {
public static void main(String[] args) {
Approver teamLeader = new TeamLeader();
Approver departmentManager = new DepartmentManager();
Approver ceo = new CEO();
teamLeader.setNextApprover(departmentManager);
departmentManager.setNextApprover(ceo);
// 发起请假申请
teamLeader.approveLeave(3);
System.out.println("-----");
teamLeader.approveLeave(6);
System.out.println("-----");
teamLeader.approveLeave(12);
}
}
在上面的示例中,我们使用责任链模式实现了一个请假审批系统。通过将审批人对象依次连接起来,形成一个责任链,每个审批人对象都可以处理请求或者将请求传递给下一个审批人。当有请假申请时,从责任链的头部开始处理请求,直到找到能够处理请求的审批人为止。
责任链模式的优点是将请求发送者和请求处理者解耦,提高了代码的灵活性和可扩展性。同时,责任链模式还可以动态地调整责任链的结构,增加或者删除审批人对象,以满足不同的需求。
(2)创建型:
单例模式:
单例模式保证一个类只有一个实例,并提供一个全局的访问点来访问该实例。单例模式的核心思想是通过限制类的实例化过程,确保只有一个实例存在,并提供一个静态方法来获取该实例。
下面举个例子来说明单例模式的应用场景和实现方式。
假设我们正在开发一个日志记录器,该日志记录器可以在系统的任何地方被调用来记录日志信息。为了确保日志记录器只有一个实例,我们可以使用单例模式。
首先,我们需要创建一个日志记录器类,将构造函数私有化,防止外部直接实例化该类。
public class Logger {
private static Logger instance;
private Logger() {
// 私有化构造函数
}
public static Logger getInstance() {
if (instance == null) {
instance = new Logger();
}
return instance;
}
public void log(String message) {
System.out.println("Log: " + message);
}
}
在上面的示例中,我们使用了一个静态变量 instance 来保存 Logger 类的唯一实例。在 getInstance 方法中,我们通过判断 instance 是否为 null 来决定是否创建一个新的实例。如果 instance 为 null,则创建一个新的实例;如果 instance 不为 null,则直接返回已有的实例。
接下来,我们可以在系统的任何地方调用日志记录器来记录日志。
public class Client {
public static void main(String[] args) {
Logger logger1 = Logger.getInstance();
logger1.log("This is a log message");
Logger logger2 = Logger.getInstance();
logger2.log("Another log message");
System.out.println(logger1 == logger2); // 输出 true,说明 logger1 和 logger2 是同一个实例
}
}
在上面的示例中,我们可以看到,通过调用 Logger 类的 getInstance 方法,我们可以获取到 Logger 类的唯一实例。无论在系统的任何地方创建 Logger 类的实例,都会得到同一个实例。
单例模式的优点是保证了类的唯一实例,避免了多个实例的创建和资源的浪费。同时,单例模式还提供了一个全局的访问点,方便其他类对实例的访问。然而,单例模式也有一些缺点,比如可能造成资源的长期占用,以及对单例类的扩展和测试的困难。因此,在使用单例模式时需要慎重考虑,并根据具体情况进行选择。
同时单例还有其他方式,比如懒汉模式,饿汉模式,还有枚举的方法实现
建造者模式:
建造者模式用于构建复杂对象。该模式将对象的构造过程与其表示分离,以便相同的构造过程可以创建不同的表示。
建造者模式通常包含以下几个角色:
- 产品(Product):表示被构建的复杂对象。产品类通常具有多个属性,需要通过建造者模式来逐步构建。
- 抽象建造者(Builder):定义了构建产品的步骤和接口。抽象建造者类通常包含一些构建产品的抽象方法,如构建产品的不同部分或设置产品的属性。
- 具体建造者(Concrete Builder):实现了抽象建造者接口,负责具体产品的构建过程。具体建造者类通常包含一个具体产品对象作为成员变量,并实现抽象建造者中定义的方法。
- 指挥者(Director):负责使用建造者对象构建产品。指挥者类通常包含一个建造者对象作为成员变量,并定义一个构建产品的方法,该方法按照一定的顺序调用建造者的方法来构建产品。
下面以一个简单的例子来说明建造者模式的应用。假设我们要构建一辆汽车,汽车由车身、引擎和轮子组成。首先,我们定义一个产品类Car,包含车身、引擎和轮子三个属性。然后,我们定义一个抽象建造者类CarBuilder,其中包含构建车身、构建引擎和构建轮子三个抽象方法。接下来,我们定义两个具体建造者类SUVBuilder和SedanBuilder,分别实现了CarBuilder接口。最后,我们定义一个指挥者类Director,负责调用具体建造者的方法按照一定的顺序构建产品。
// 产品类Car
class Car {
private String body;
private String engine;
private String wheel;
// getter和setter方法
}
// 抽象建造者类CarBuilder
interface CarBuilder {
void buildBody();
void buildEngine();
void buildWheel();
Car getCar();
}
// 具体建造者类SUVBuilder
class SUVBuilder implements CarBuilder {
private Car car;
public SUVBuilder() {
car = new Car();
}
public void buildBody() {
car.setBody("SUV Body");
}
public void buildEngine() {
car.setEngine("SUV Engine");
}
public void buildWheel() {
car.setWheel("SUV Wheel");
}
public Car getCar() {
return car;
}
}
// 具体建造者类SedanBuilder
class SedanBuilder implements CarBuilder {
private Car car;
public SedanBuilder() {
car = new Car();
}
public void buildBody() {
car.setBody("Sedan Body");
}
public void buildEngine() {
car.setEngine("Sedan Engine");
}
public void buildWheel() {
car.setWheel("Sedan Wheel");
}
public Car getCar() {
return car;
}
}
// 指挥者类Director
class Director {
private CarBuilder builder;
public void setBuilder(CarBuilder builder) {
this.builder = builder;
}
public Car constructCar() {
builder.buildBody();
builder.buildEngine();
builder.buildWheel();
return builder.getCar();
}
}
// 客户端代码
public class Client {
public static void main(String[] args) {
Director director = new Director();
CarBuilder suvBuilder = new SUVBuilder();
CarBuilder sedanBuilder = new SedanBuilder();
director.setBuilder(suvBuilder);
Car suv = director.constructCar();
System.out.println(suv);
director.setBuilder(sedanBuilder);
Car sedan = director.constructCar();
System.out.println(sedan);
}
}
上述代码中,我们通过建造者模式来构建了两辆不同类型的汽车,分别为SUV和轿车(Sedan)。指挥者类Director负责调用具体建造者的方法按照一定的顺序构建汽车,最终返回构建好的产品。这样,我们可以根据需要灵活地构建不同类型的汽车,而无需关心具体的构建过程。
原型模式:
原型模式通过复制(克隆)现有对象来创建新的对象,而无需通过实例化类来创建。原型模式允许我们创建一个可定制的对象副本,而无需知道其具体的创建细节。
原型模式通常包含以下几个角色:
- 原型(Prototype):定义了克隆自身的方法。在原型模式中,原型对象是被复制(克隆)的对象。
- 具体原型(Concrete Prototype):实现了原型接口,实现了克隆自身的方法。具体原型类通常包含一个克隆方法,用于复制自身并返回一个新的对象。
下面以一个简单的例子来说明原型模式的应用。假设我们要创建一个图形类,包含形状和颜色两个属性。我们可以使用原型模式来创建不同形状和颜色的图形对象。
首先,我们定义一个原型接口Shape,其中包含一个克隆自身的方法clone()。然后,我们定义具体原型类Circle和Rectangle,分别实现了Shape接口,并实现了克隆自身的方法。
// 原型接口Shape
interface Shape extends Cloneable {
Shape clone();
void draw();
}
// 具体原型类Circle
class Circle implements Shape {
private String color;
public Circle(String color) {
this.color = color;
}
public Shape clone() {
return new Circle(this.color);
}
public void draw() {
System.out.println("Drawing a circle with color: " + color);
}
}
// 具体原型类Rectangle
class Rectangle implements Shape {
private String color;
public Rectangle(String color) {
this.color = color;
}
public Shape clone() {
return new Rectangle(this.color);
}
public void draw() {
System.out.println("Drawing a rectangle with color: " + color);
}
}
// 客户端代码
public class Client {
public static void main(String[] args) {
Shape circle = new Circle("Red");
Shape rectangle = new Rectangle("Blue");
Shape clonedCircle = circle.clone();
Shape clonedRectangle = rectangle.clone();
circle.draw();
clonedCircle.draw();
rectangle.draw();
clonedRectangle.draw();
}
}
上述代码中,我们通过原型模式创建了两个不同颜色的图形对象,一个是圆形(Circle),一个是矩形(Rectangle)。在客户端代码中,我们首先创建了原始的圆形和矩形对象,并分别调用它们的draw()方法来绘制图形。然后,我们使用克隆方法clone()创建了它们的副本,再次调用其draw()方法来绘制克隆的图形。通过原型模式,我们可以复制(克隆)现有的对象,而无需重新实例化类来创建新的对象,从而提高了效率并允许我们创建定制的对象副本。
工厂方法:
工厂方法模式定义了一个用于创建对象的接口,但由子类决定要实例化的类是哪一个。工厂方法模式将对象的实例化延迟到子类中进行,从而实现了对象的创建和使用的解耦。
工厂方法模式通常包含以下几个角色:
- 抽象产品(Product):定义了产品的接口。抽象产品类是具体产品类的父类,通常包含一些抽象方法,用于描述产品的行为。
- 具体产品(Concrete Product):实现了抽象产品接口,具体产品类是工厂方法模式的创建目标,即需要被创建的对象。
- 抽象工厂(Factory):定义了创建产品的接口。抽象工厂类通常包含一个工厂方法,用于创建具体产品的对象。
- 具体工厂(Concrete Factory):实现了抽象工厂接口,具体工厂类负责实例化具体产品。
下面以一个简单的例子来说明工厂方法模式的应用。假设我们要创建一个图形类,包含形状和颜色两个属性。我们可以使用工厂方法模式来创建不同类型的图形对象。
首先,我们定义一个抽象产品接口Shape,其中包含一个方法用于绘制图形。然后,我们定义具体产品类Circle和Rectangle,分别实现了Shape接口,并实现了绘制图形的方法。接下来,我们定义一个抽象工厂接口ShapeFactory,其中包含一个工厂方法用于创建具体产品。最后,我们定义具体工厂类CircleFactory和RectangleFactory,分别实现了ShapeFactory接口,负责创建具体产品。
// 抽象产品接口Shape
interface Shape {
void draw();
}
// 具体产品类Circle
class Circle implements Shape {
public void draw() {
System.out.println("Drawing a circle");
}
}
// 具体产品类Rectangle
class Rectangle implements Shape {
public void draw() {
System.out.println("Drawing a rectangle");
}
}
// 抽象工厂接口ShapeFactory
interface ShapeFactory {
Shape createShape();
}
// 具体工厂类CircleFactory
class CircleFactory implements ShapeFactory {
public Shape createShape() {
return new Circle();
}
}
// 具体工厂类RectangleFactory
class RectangleFactory implements ShapeFactory {
public Shape createShape() {
return new Rectangle();
}
}
// 客户端代码
public class Client {
public static void main(String[] args) {
ShapeFactory circleFactory = new CircleFactory();
Shape circle = circleFactory.createShape();
circle.draw();
ShapeFactory rectangleFactory = new RectangleFactory();
Shape rectangle = rectangleFactory.createShape();
rectangle.draw();
}
}
上述代码中,我们通过工厂方法模式创建了两个不同类型的图形对象,一个是圆形(Circle),一个是矩形(Rectangle)。在客户端代码中,我们首先创建了具体工厂类CircleFactory和RectangleFactory,并分别调用它们的工厂方法createShape()来创建具体产品。然后,我们调用具体产品的draw()方法来绘制图形。通过工厂方法模式,我们可以通过抽象工厂接口来创建具体产品对象,从而实现了对象的创建和使用的解耦,提高了代码的可扩展性和灵活性。
抽象工厂方法:
抽象工厂模式提供了一种封装一组相关或相互依赖对象的接口,而无需指定它们具体的类。抽象工厂模式通过提供一个抽象工厂接口,定义了一系列工厂方法,用于创建一系列相关的产品对象。
抽象工厂模式通常包含以下几个角色:
- 抽象产品接口(Abstract Product):定义了产品的接口。抽象产品类是具体产品类的父类,通常包含一些抽象方法,用于描述产品的行为。
- 具体产品类(Concrete Product):实现了抽象产品接口,具体产品类是抽象工厂模式的创建目标,即需要被创建的对象。
- 抽象工厂接口(Abstract Factory):定义了创建产品的接口。抽象工厂类通常包含一组工厂方法,用于创建一系列相关的产品对象。
- 具体工厂类(Concrete Factory):实现了抽象工厂接口,具体工厂类负责实例化一系列相关的具体产品。
下面以一个简单的例子来说明抽象工厂模式的应用。假设我们要创建一个图形和颜色的组合,我们可以使用抽象工厂模式来创建不同类型的图形和颜色对象。
首先,我们定义一个抽象产品接口Shape,其中包含一个方法用于绘制图形。然后,我们定义具体产品类Circle和Rectangle,分别实现了Shape接口,并实现了绘制图形的方法。接下来,我们定义另一个抽象产品接口Color,其中也包含一个方法用于填充颜色。然后,我们定义具体产品类Red和Blue,分别实现了Color接口,并实现了填充颜色的方法。接着,我们定义一个抽象工厂接口AbstractFactory,其中包含一组工厂方法,分别用于创建图形和颜色对象。最后,我们定义具体工厂类ShapeFactory和ColorFactory,分别实现了AbstractFactory接口,负责创建具体产品。
// 抽象产品接口Shape
interface Shape {
void draw();
}
// 具体产品类Circle
class Circle implements Shape {
public void draw() {
System.out.println("Drawing a circle");
}
}
// 具体产品类Rectangle
class Rectangle implements Shape {
public void draw() {
System.out.println("Drawing a rectangle");
}
}
// 抽象产品接口Color
interface Color {
void fill();
}
// 具体产品类Red
class Red implements Color {
public void fill() {
System.out.println("Filling with red color");
}
}
// 具体产品类Blue
class Blue implements Color {
public void fill() {
System.out.println("Filling with blue color");
}
}
// 抽象工厂接口AbstractFactory
interface AbstractFactory {
Shape createShape();
Color createColor();
}
// 具体工厂类ShapeFactory
class ShapeFactory implements AbstractFactory {
public Shape createShape() {
return new Circle();
}
public Color createColor() {
return new Red();
}
}
// 具体工厂类ColorFactory
class ColorFactory implements AbstractFactory {
public Shape createShape() {
return new Rectangle();
}
public Color createColor() {
return new Blue();
}
}
// 客户端代码
public class Client {
public static void main(String[] args) {
AbstractFactory shapeFactory = new ShapeFactory();
Shape shape = shapeFactory.createShape();
shape.draw();
AbstractFactory colorFactory = new ColorFactory();
Color color = colorFactory.createColor();
color.fill();
}
}
上述代码中,我们通过抽象工厂模式创建了两个不同类型的对象组合,一个是圆形(Circle)和红色(Red),一个是矩形(Rectangle)和蓝色(Blue)。在客户端代码中,我们首先创建了具体工厂类ShapeFactory和ColorFactory,并分别调用它们的工厂方法createShape()和createColor()来创建具体产品。然后,我们调用具体产品的方法来绘制图形和填充颜色。通过抽象工厂模式,我们可以通过抽象工厂接口来创建一系列相关的产品对象组合,从而实现了对象的创建和使用的解耦,提高了代码的可扩展性和灵活性。
(3)结构型:
组合模式:
组合模式允许我们将对象组合成树形结构以表示“部分-整体”的层次结构。组合模式使得客户端可以统一对待单个对象和组合对象,从而简化了客户端的代码。
组合模式通常包含以下几个角色:
- 组件(Component):定义了组合对象和叶子对象的共同接口。组件可以是接口或抽象类,其中包含了一些公共方法,如添加、删除和获取子节点等。
- 叶子组件(Leaf):表示组合中的叶子对象,它没有子节点。叶子组件实现了组件接口的方法,但通常不包含子节点的操作。
- 组合组件(Composite):表示组合中的组合对象,它可以包含其他组件或叶子对象作为子节点。组合组件实现了组件接口的方法,并维护了一个子节点列表。
下面以一个简单的例子来说明组合模式的应用。假设我们要构建一个文件系统,其中包含文件和文件夹两种类型的节点。我们可以使用组合模式来创建文件和文件夹的层次结构。
首先,我们定义一个组件接口Component,其中包含了一些公共方法,如添加子节点、删除子节点和获取子节点等。然后,我们定义具体的叶子组件类File,表示文件节点。接着,我们定义具体的组合组件类Folder,表示文件夹节点。在Folder类中,我们维护了一个子节点列表,并实现了Component接口的方法。
// 组件接口Component
interface Component {
void add(Component component);
void remove(Component component);
Component getChild(int index);
void display();
}
// 叶子组件类File
class File implements Component {
private String name;
public File(String name) {
this.name = name;
}
public void add(Component component) {
// 叶子节点不支持添加操作
}
public void remove(Component component) {
// 叶子节点不支持删除操作
}
public Component getChild(int index) {
// 叶子节点没有子节点
return null;
}
public void display() {
System.out.println("File: " + name);
}
}
// 组合组件类Folder
class Folder implements Component {
private String name;
private List<Component> children;
public Folder(String name) {
this.name = name;
this.children = new ArrayList<>();
}
public void add(Component component) {
children.add(component);
}
public void remove(Component component) {
children.remove(component);
}
public Component getChild(int index) {
return children.get(index);
}
public void display() {
System.out.println("Folder: " + name);
for (Component component : children) {
component.display();
}
}
}
// 客户端代码
public class Client {
public static void main(String[] args) {
Component file1 = new File("file1.txt");
Component file2 = new File("file2.txt");
Component folder1 = new Folder("folder1");
folder1.add(file1);
folder1.add(file2);
Component file3 = new File("file3.txt");
Component file4 = new File("file4.txt");
Component folder2 = new Folder("folder2");
folder2.add(file3);
folder2.add(file4);
folder2.add(folder1);
folder2.display();
}
}
上述代码中,我们使用组合模式构建了一个简单的文件系统。我们创建了两个文件file1.txt和file2.txt,并将它们添加到名为folder1的文件夹节点中。然后,我们创建了两个文件file3.txt和file4.txt,并将它们添加到名为folder2的文件夹节点中。最后,我们将folder1添加到folder2中,并调用folder2的display()方法来显示整个文件系统的层次结构。
通过组合模式,我们可以将文件和文件夹组织成树形结构,并使用统一的方式对待单个文件和文件夹。这种结构使得我们可以方便地进行操作,如添加、删除、遍历等,并且能够灵活地扩展和组织对象。
代理模式:
代理模式提供了一个代理对象,可以控制对其他对象的访问。代理模式通过在代理对象中创建一个与被代理对象相同的接口,从而使得客户端可以通过代理对象来访问被代理对象。
代理模式通常包含以下几个角色:
- 抽象主题(Subject):定义了被代理对象和代理对象的共同接口,这样代理对象可以替代被代理对象。
- 真实主题(Real Subject):表示被代理的对象,是真正执行业务逻辑的对象。
- 代理(Proxy):代理对象,实现了抽象主题接口,并在其中维护了一个对真实主题对象的引用。代理对象在执行其方法时,可以在调用前后添加一些额外的逻辑。
下面以一个简单的例子来说明代理模式的应用。假设我们要访问一个远程服务器上的图片,为了提高访问的效率,我们可以使用代理模式来创建一个代理对象,控制对远程服务器的访问。
首先,我们定义一个抽象主题接口Image,其中包含了一个显示图片的方法。然后,我们定义一个真实主题类RealImage,表示远程服务器上的图片。在RealImage类中,我们实现了Image接口的方法,并在其中添加了加载图片的逻辑。接着,我们定义一个代理类ProxyImage,实现了Image接口,并在其中维护了一个对RealImage对象的引用。在ProxyImage类中,我们在调用显示图片的方法时,先判断是否已经加载了图片,如果没有则先加载图片,然后再调用真实主题对象的显示方法。
// 抽象主题接口Image
interface Image {
void display();
}
// 真实主题类RealImage
class RealImage implements Image {
private String filename;
public RealImage(String filename) {
this.filename = filename;
load();
}
private void load() {
System.out.println("Loading image: " + filename);
}
public void display() {
System.out.println("Displaying image: " + filename);
}
}
// 代理类ProxyImage
class ProxyImage implements Image {
private String filename;
private RealImage realImage;
public ProxyImage(String filename) {
this.filename = filename;
}
public void display() {
if (realImage == null) {
realImage = new RealImage(filename);
}
realImage.display();
}
}
// 客户端代码
public class Client {
public static void main(String[] args) {
Image image1 = new ProxyImage("image1.png");
image1.display();
Image image2 = new ProxyImage("image2.png");
image2.display();
image2.display(); // 不会重新加载图片,直接显示
}
}
上述代码中,我们使用代理模式来访问远程服务器上的图片。我们创建了两个代理对象ProxyImage,分别表示image1.png和image2.png图片。在客户端代码中,第一次调用image1.display()时,代理对象会创建一个真实主题对象RealImage,并加载图片,然后调用真实主题对象的显示方法。第一次调用image2.display()时,代理对象会创建另一个真实主题对象,并加载另一张图片,然后再调用真实主题对象的显示方法。第二次调用image2.display()时,代理对象不会重新加载图片,直接调用真实主题对象的显示方法。
通过代理模式,我们可以在访问真实对象之前或之后添加一些额外的逻辑,如延迟加载、权限控制、缓存等。代理模式提供了一种间接访问对象的方式,可以在不改变原有代码的情况下增加一些功能。
装饰模式:
装饰模式允许我们动态地为对象添加额外的功能,而无需修改其原始类。装饰模式通过创建一个装饰器类,将被装饰对象作为参数传递给装饰器对象,从而实现对被装饰对象的包装。
装饰模式通常包含以下几个角色:
- 抽象构件(Component):定义了被装饰对象和装饰器对象的共同接口,这样装饰器对象可以替代被装饰对象。
- 具体构件(Concrete Component):表示被装饰的对象,是真正执行业务逻辑的对象。
- 抽象装饰器(Decorator):继承或实现了抽象构件接口,并在其中维护了一个对抽象构件的引用。抽象装饰器类中通常会定义一些额外的方法或属性。
- 具体装饰器(Concrete Decorator):实现了抽象装饰器接口,并在其中添加了额外的逻辑或功能。
下面以一个简单的例子来说明装饰模式的应用。假设我们要为一杯咖啡添加额外的配料,如牛奶、糖浆或巧克力。我们可以使用装饰模式来创建一个装饰器对象,将咖啡对象作为参数传递给装饰器对象,从而实现对咖啡对象的包装。
首先,我们定义一个抽象构件接口Beverage,其中包含了获取描述和计算价格的方法。然后,我们定义具体构件类Coffee,表示咖啡对象。在Coffee类中,我们实现了Beverage接口的方法,并在其中返回咖啡的描述和价格。接着,我们定义一个抽象装饰器类CondimentDecorator,继承自Beverage接口,并在其中维护了一个对Beverage对象的引用。在CondimentDecorator类中,我们可以根据需要添加额外的配料,并在计算价格时加上配料的价格。最后,我们定义具体装饰器类Milk、Syrup和Chocolate,分别表示牛奶、糖浆和巧克力配料。在这些具体装饰器类中,我们实现了CondimentDecorator接口的方法,并在其中添加了对应的配料。
// 抽象构件接口Beverage
interface Beverage {
String getDescription();
double getPrice();
}
// 具体构件类Coffee
class Coffee implements Beverage {
public String getDescription() {
return "Coffee";
}
public double getPrice() {
return 2.0;
}
}
// 抽象装饰器类CondimentDecorator
abstract class CondimentDecorator implements Beverage {
protected Beverage beverage;
public CondimentDecorator(Beverage beverage) {
this.beverage = beverage;
}
}
// 具体装饰器类Milk
class Milk extends CondimentDecorator {
public Milk(Beverage beverage) {
super(beverage);
}
public String getDescription() {
return beverage.getDescription() + ", Milk";
}
public double getPrice() {
return beverage.getPrice() + 0.5;
}
}
// 具体装饰器类Syrup
class Syrup extends CondimentDecorator {
public Syrup(Beverage beverage) {
super(beverage);
}
public String getDescription() {
return beverage.getDescription() + ", Syrup";
}
public double getPrice() {
return beverage.getPrice() + 0.3;
}
}
// 具体装饰器类Chocolate
class Chocolate extends CondimentDecorator {
public Chocolate(Beverage beverage) {
super(beverage);
}
public String getDescription() {
return beverage.getDescription() + ", Chocolate";
}
public double getPrice() {
return beverage.getPrice() + 0.7;
}
}
// 客户端代码
public class Client {
public static void main(String[] args) {
Beverage coffee = new Coffee();
System.out.println(coffee.getDescription() + ": $" + coffee.getPrice());
Beverage coffeeWithMilk = new Milk(coffee);
System.out.println(coffeeWithMilk.getDescription() + ": $" + coffeeWithMilk.getPrice());
Beverage coffeeWithMilkAndSyrup = new Syrup(coffeeWithMilk);
System.out.println(coffeeWithMilkAndSyrup.getDescription() + ": $" + coffeeWithMilkAndSyrup.getPrice());
Beverage coffeeWithMilkAndSyrupAndChocolate = new Chocolate(coffeeWithMilkAndSyrup);
System.out.println(coffeeWithMilkAndSyrupAndChocolate.getDescription() + ": $" + coffeeWithMilkAndSyrupAndChocolate.getPrice());
}
}
上述代码中,我们使用装饰模式为一杯咖啡添加了牛奶、糖浆和巧克力配料。我们首先创建了一个具体构件对象Coffee,表示咖啡。然后,我们创建了一个具体装饰器对象Milk,将咖啡对象作为参数传递给装饰器对象,从而将咖啡对象包装成带有牛奶的咖啡。接着,我们创建了另一个具体装饰器对象Syrup,将带有牛奶的咖啡对象作为参数传递给装饰器对象,从而将咖啡对象再次包装成带有牛奶和糖浆的咖啡。最后,我们创建了第三个具体装饰器对象Chocolate,将带有牛奶和糖浆的咖啡对象作为参数传递给装饰器对象,从而将咖啡对象再次包装成带有牛奶、糖浆和巧克力的咖啡。在客户端代码中,我们分别调用每个对象的getDescription()和getPrice()方法来获取描述和价格。
通过装饰模式,我们可以动态地为对象添加额外的功能,而无需修改其原始类。这种结构使得我们可以方便地组合和扩展对象,同时遵循开闭原则,将变化封装在装饰器中。
外观模式:
外观模式是一种结构型设计模式,它提供了一个统一的接口,用于访问子系统中的一组接口。外观模式通过创建一个外观类,将客户端与子系统之间的复杂关系解耦,从而简化了客户端的代码。
外观模式通常包含以下几个角色:
- 外观(Facade):外观类,提供了一个统一的接口,用于访问子系统中的一组接口。外观类将客户端的请求委派给子系统内部的相应对象进行处理。
- 子系统(Subsystem):子系统类,实现了子系统中各个接口的具体实现。子系统类负责处理外观类委派过来的请求。
下面以一个简单的例子来说明外观模式的应用。假设我们要设计一个音响系统,其中包含了音响、投影仪和灯光等子系统。为了简化客户端的代码,我们可以使用外观模式来创建一个外观类,将客户端与子系统之间的复杂关系封装起来。
首先,我们定义一个外观类HomeTheaterFacade,其中包含了一组方法,用于控制音响、投影仪和灯光等子系统。在HomeTheaterFacade类中,我们创建了音响、投影仪和灯光等子系统的对象,并在每个方法中调用相应的子系统方法来完成具体的操作。
// 外观类HomeTheaterFacade
class HomeTheaterFacade {
private Amplifier amplifier;
private Projector projector;
private Lights lights;
public HomeTheaterFacade() {
this.amplifier = new Amplifier();
this.projector = new Projector();
this.lights = new Lights();
}
public void watchMovie() {
System.out.println("Get ready to watch a movie!");
amplifier.turnOn();
projector.turnOn();
lights.dim();
}
public void endMovie() {
System.out.println("Shutting down the home theater!");
amplifier.turnOff();
projector.turnOff();
lights.turnOn();
}
}
// 子系统类Amplifier
class Amplifier {
public void turnOn() {
System.out.println("Amplifier is turned on.");
}
public void turnOff() {
System.out.println("Amplifier is turned off.");
}
}
// 子系统类Projector
class Projector {
public void turnOn() {
System.out.println("Projector is turned on.");
}
public void turnOff() {
System.out.println("Projector is turned off.");
}
}
// 子系统类Lights
class Lights {
public void turnOn() {
System.out.println("Lights are turned on.");
}
public void dim() {
System.out.println("Lights are dimmed.");
}
}
// 客户端代码
public class Client {
public static void main(String[] args) {
HomeTheaterFacade homeTheater = new HomeTheaterFacade();
homeTheater.watchMovie();
// Do something else...
homeTheater.endMovie();
}
}
上述代码中,我们使用外观模式创建了一个音响系统的外观类HomeTheaterFacade。在客户端代码中,我们创建了一个HomeTheaterFacade对象,并调用其watchMovie()方法来准备观影,然后调用endMovie()方法来结束观影。在watchMovie()方法中,外观类将调用音响、投影仪和灯光等子系统的相应方法来完成具体的操作。在endMovie()方法中,外观类同样将调用子系统的方法来完成操作。
通过外观模式,客户端只需要与外观类进行交互,无需了解子系统的具体实现细节。外观模式将复杂的子系统封装起来,使得客户端的代码更加简洁和易于维护。同时,外观模式也提供了一种灵活的方式来访问子系统的接口,可以根据需要自由组合和调用接口。
桥接模式:
桥接模式将抽象部分与实现部分分离,使它们可以独立地变化。桥接模式通过创建一个桥接接口,将抽象部分与实现部分连接起来,从而实现了抽象部分和实现部分的解耦。
桥接模式通常包含以下几个角色:
- 抽象部分(Abstraction):定义了抽象部分的接口,并维护了一个对实现部分的引用。抽象部分中的方法通常会调用实现部分的方法。
- 具体抽象部分(Concrete Abstraction):实现了抽象部分接口,并在其中实现了抽象部分的方法。
- 实现部分(Implementor):定义了实现部分的接口。实现部分中的方法通常是具体的实现逻辑。
- 具体实现部分(Concrete Implementor):实现了实现部分接口,并在其中实现了实现部分的方法。
下面以一个简单的例子来说明桥接模式的应用。假设我们要设计一个跨平台的图像绘制程序,可以在不同的操作系统上绘制图像,如Windows、Linux和Mac。我们可以使用桥接模式来将图像类型和平台类型分离,使它们可以独立地变化。
首先,我们定义一个抽象部分接口Shape,其中包含了绘制图像的方法。然后,我们定义一个实现部分接口Platform,其中包含了绘制图像所需的操作系统方法。接着,我们创建具体实现部分类Windows、Linux和Mac,分别表示Windows平台、Linux平台和Mac平台。在具体实现部分类中,我们实现了Platform接口的方法,并在其中添加了具体的实现逻辑。最后,我们创建具体抽象部分类Rectangle、Circle和Triangle,分别表示矩形、圆形和三角形。在具体抽象部分类中,我们实现了Shape接口的方法,并在其中调用实现部分的方法。
// 抽象部分接口Shape
interface Shape {
void draw();
}
// 具体抽象部分类Rectangle
class Rectangle implements Shape {
private Platform platform;
public Rectangle(Platform platform) {
this.platform = platform;
}
public void draw() {
System.out.print("Drawing a rectangle on ");
platform.draw();
}
}
// 具体抽象部分类Circle
class Circle implements Shape {
private Platform platform;
public Circle(Platform platform) {
this.platform = platform;
}
public void draw() {
System.out.print("Drawing a circle on ");
platform.draw();
}
}
// 具体抽象部分类Triangle
class Triangle implements Shape {
private Platform platform;
public Triangle(Platform platform) {
this.platform = platform;
}
public void draw() {
System.out.print("Drawing a triangle on ");
platform.draw();
}
}
// 实现部分接口Platform
interface Platform {
void draw();
}
// 具体实现部分类Windows
class Windows implements Platform {
public void draw() {
System.out.println("Windows");
}
}
// 具体实现部分类Linux
class Linux implements Platform {
public void draw() {
System.out.println("Linux");
}
}
// 具体实现部分类Mac
class Mac implements Platform {
public void draw() {
System.out.println("Mac");
}
}
// 客户端代码
public class Client {
public static void main(String[] args) {
Shape rectangle = new Rectangle(new Windows());
rectangle.draw();
Shape circle = new Circle(new Linux());
circle.draw();
Shape triangle = new Triangle(new Mac());
triangle.draw();
}
}
上述代码中,我们使用桥接模式创建了一个跨平台的图像绘制程序。在客户端代码中,我们创建了具体抽象部分对象Rectangle、Circle和Triangle,并将具体实现部分对象Windows、Linux和Mac作为参数传递给具体抽象部分对象。然后,我们分别调用具体抽象部分对象的draw()方法来绘制图像。在draw()方法中,具体抽象部分对象会调用具体实现部分对象的draw()方法来完成具体的绘制操作。
通过桥接模式,我们将抽象部分和实现部分分离,使它们可以独立地变化。这种结构可以提高系统的灵活性和可扩展性,可以根据需要自由组合和调用不同的抽象部分和实现部分。同时,桥接模式也符合开闭原则,将变化封装在桥接接口中,使得系统的扩展更加容易。
享元模式:
享元模式通过共享对象来减少内存使用和提高性能。享元模式的核心思想是将对象分为可共享的内部状态和不可共享的外部状态。通过共享内部状态,可以减少对内存的消耗。
享元模式通常包含以下几个角色:
- 享元工厂(Flyweight Factory):负责创建和管理享元对象。享元工厂通常会维护一个享元池(Flyweight Pool)来存储已经创建的享元对象。
- 享元(Flyweight):表示可共享的对象。享元对象通常包含内部状态和外部状态两部分。内部状态是不可变的,可以被多个对象共享。外部状态是可变的,每个对象都有自己的外部状态。
- 客户端(Client):使用享元对象的客户端。客户端可以通过享元工厂来获取共享的享元对象,并根据需要设置外部状态。
下面以一个简单的例子来说明享元模式的应用。假设我们要设计一个棋盘游戏,其中包含了不同颜色的棋子。为了节省内存,我们可以使用享元模式来共享相同颜色的棋子对象。
首先,我们定义一个享元接口ChessPiece,其中包含了一个方法display(),用于显示棋子的颜色和位置。然后,我们创建具体享元类ConcreteChessPiece,实现了ChessPiece接口,并在其中实现了display()方法。在display()方法中,我们将输出棋子的颜色和位置。接着,我们创建享元工厂类ChessPieceFactory,用于创建和管理享元对象。在ChessPieceFactory类中,我们维护了一个享元池,用于存储已经创建的享元对象。在获取享元对象时,我们首先检查是否已经存在相同颜色的棋子对象,如果存在则直接返回,否则创建新的棋子对象并添加到享元池中。最后,我们创建客户端类Client,使用享元模式来绘制棋盘。
// 享元接口ChessPiece
interface ChessPiece {
void display(int x, int y);
}
// 具体享元类ConcreteChessPiece
class ConcreteChessPiece implements ChessPiece {
private String color;
public ConcreteChessPiece(String color) {
this.color = color;
}
public void display(int x, int y) {
System.out.println("Displaying " + color + " chess piece at (" + x + ", " + y + ").");
}
}
// 享元工厂类ChessPieceFactory
class ChessPieceFactory {
private Map<String, ChessPiece> chessPieces;
public ChessPieceFactory() {
this.chessPieces = new HashMap<>();
}
public ChessPiece getChessPiece(String color) {
if (chessPieces.containsKey(color)) {
return chessPieces.get(color);
} else {
ChessPiece chessPiece = new ConcreteChessPiece(color);
chessPieces.put(color, chessPiece);
return chessPiece;
}
}
}
// 客户端类Client
public class Client {
public static void main(String[] args) {
ChessPieceFactory chessPieceFactory = new ChessPieceFactory();
ChessPiece blackChessPiece = chessPieceFactory.getChessPiece("black");
blackChessPiece.display(2, 3);
ChessPiece whiteChessPiece = chessPieceFactory.getChessPiece("white");
whiteChessPiece.display(5, 6);
ChessPiece anotherBlackChessPiece = chessPieceFactory.getChessPiece("black");
anotherBlackChessPiece.display(4, 1);
}
}
上述代码中,我们使用享元模式实现了一个棋盘游戏。在客户端代码中,我们首先创建了一个享元工厂对象ChessPieceFactory。然后,我们通过调用享元工厂的getChessPiece()方法来获取棋子对象。在获取棋子对象时,我们首先检查是否已经存在相同颜色的棋子对象,如果存在则直接返回,否则创建新的棋子对象并添加到享元池中。最后,我们调用棋子对象的display()方法来显示棋子的颜色和位置。
通过享元模式,我们可以减少相同颜色的棋子对象的创建,从而节省内存的消耗。享元模式将内部状态和外部状态分离,使得享元对象可以被多个客户端共享。这种结构可以提高系统的性能和扩展性,特别适用于需要创建大量相似对象的场景。
适配器模式:
适配器模式用于将一个类的接口转换成另一个类的接口,从而使原本不兼容的类能够一起工作。适配器模式通过创建一个适配器类,将原始类的接口转换成目标类所期望的接口,从而实现两个类的协同工作。
适配器模式通常包含以下几个角色:
- 目标接口(Target):定义了目标类所期望的接口。适配器类会实现这个接口,将原始类的接口转换成目标接口。
- 原始类(Adaptee):需要被适配的类。原始类的接口与目标接口不兼容。
- 适配器类(Adapter):实现了目标接口,并包含一个原始类的引用。适配器类的方法通常会调用原始类的方法,将其转换为目标接口的方法。
下面以一个简单的例子来说明适配器模式的应用。假设我们有一个音乐播放器,可以播放MP3格式的音乐,但我们想要扩展它的功能,以便可以播放其他格式的音乐,如MP4和WAV。我们可以使用适配器模式来实现这个功能。
首先,我们定义一个目标接口MediaPlayer,其中包含了播放音乐的方法playMusic()。然后,我们创建一个原始类MP3Player,它实现了目标接口MediaPlayer,可以播放MP3格式的音乐。接着,我们创建一个适配器类MediaAdapter,它也实现了目标接口MediaPlayer,并包含一个原始类MP4Player的引用。在MediaAdapter类的playMusic()方法中,我们调用原始类MP4Player的playMP4()方法,将其转换为目标接口MediaPlayer的playMusic()方法。最后,我们创建客户端类Client,使用适配器模式来播放不同格式的音乐。
// 目标接口MediaPlayer
interface MediaPlayer {
void playMusic(String fileName);
}
// 原始类MP3Player
class MP3Player implements MediaPlayer {
public void playMusic(String fileName) {
System.out.println("Playing MP3 music: " + fileName);
}
}
// 原始类MP4Player
class MP4Player {
public void playMP4(String fileName) {
System.out.println("Playing MP4 music: " + fileName);
}
}
// 适配器类MediaAdapter
class MediaAdapter implements MediaPlayer {
private MP4Player mp4Player;
public MediaAdapter(MP4Player mp4Player) {
this.mp4Player = mp4Player;
}
public void playMusic(String fileName) {
mp4Player.playMP4(fileName);
}
}
// 客户端类Client
public class Client {
public static void main(String[] args) {
MediaPlayer mp3Player = new MP3Player();
mp3Player.playMusic("song.mp3");
MediaPlayer mp4Player = new MediaAdapter(new MP4Player());
mp4Player.playMusic("video.mp4");
}
}
上述代码中,我们使用适配器模式实现了一个音乐播放器。在客户端代码中,我们首先创建了一个原始类MP3Player的对象,并调用其playMusic()方法来播放MP3格式的音乐。然后,我们创建了一个适配器类MediaAdapter的对象,并将原始类MP4Player的对象作为参数传递给适配器类的构造函数。然后,我们调用适配器类的playMusic()方法来播放MP4格式的音乐。在playMusic()方法中,适配器类会调用原始类MP4Player的playMP4()方法,将其转换为目标接口MediaPlayer的playMusic()方法。
通过适配器模式,我们可以将原始类的接口转换成目标接口,使得原本不兼容的类能够一起工作。适配器模式可以提高系统的灵活性和可扩展性,特别适用于需要集成多个不兼容接口的场景。
- 总结
设计模式是一种解决软件设计问题的经验总结,它能够提供通用的解决方案,帮助开发人员编写高质量的代码。在使用设计模式时,要理解设计模式的原则和思想,并根据具体的问题和需求选择合适的设计模式。同时,要遵循设计模式的规范和约定,灵活运用设计模式,但其实所有的设计模式并非完全符合六大原则, 都是基于一些需求和实际开发遇到的情况做的取舍,因此在开发过程中,要及时沟通,以确保所有成员对设计模式的应用的理解保持一致。