观察者模式
-
观察者(Observer)模式的定义:指多个对象间存在一对多的依赖关系,当一个对象的状态发生改变时,所有依赖于它的对象都得到通知并被自动更新。这种模式有时又称作发布-订阅模式、模型-视图模式,它是对象行为型模式。
-
观察者模式是一种对象行为型模式,其主要
优点
如下。
降低了目标与观察者之间的耦合关系,两者之间是抽象耦合关系。 目标与观察者之间建立了一套触发机制。
它的主要缺点
如下。
目标与观察者之间的依赖关系并没有完全解除,而且有可能出现循环引用。 当观察者对象很多时,通知的发布会花费很多时间,影响程序的效率。 -
观察者模式可以用一个报纸订阅的例子来解释。
想象一下,你是一家报社,拥有一份报纸,而有许多读者对这份报纸感兴趣并希望及时收到新闻更新。这里,报社就是被观察者(Subject),而读者就是观察者(Observer)。
具体来说,报社(被观察者)维护了一个读者列表,当报纸有新的内容更新时,报社会通知所有订阅了报纸的读者(观察者),使他们能够及时获取最新的新闻。
// 定义观察者接口
interface IObserver
{
void Update(string message);
}
// 定义被观察者接口
interface ISubject
{
void RegisterObserver(IObserver observer);
void RemoveObserver(IObserver observer);
void NotifyObservers(string message);
}
// 实现被观察者接口
class AttendanceCounter : ISubject
{
private List<IObserver> observers;
private bool isCounting = false;
private bool userCancel = false;
public AttendanceCounter()
{
observers = new List<IObserver>();
}
public void RegisterObserver(IObserver observer)
{
observers.Add(observer);
}
public void RemoveObserver(IObserver observer)
{
observers.Remove(observer);
}
public void NotifyObservers(string message)
{
foreach (IObserver observer in observers)
{
observer.Update(message);
}
}
public void CountAttendance()
{
if (isCounting)
return;
isCounting = true;
userCancel = false;
NotifyObservers("统计开始");
try
{
ClearOldLogs();
//...........
NotifyObservers("统计完毕");
}
catch (Exception ex)
{
NotifyObservers("统计出错:" + ex.Message);
}
isCounting = false;
}
public void CancelCounting()
{
userCancel = true;
}
private void ClearOldLogs()
{
// 清除旧的日志
// ...
}
}
// 实现观察者接口
class LogWriter : IObserver
{
public void Update(string message)
{
// 写入日志
Console.WriteLine(message);
}
}
// 实现观察者接口
class RichTextWriter : IObserver
{
public void Update(string message)
{
// 写入文本框
Console.WriteLine(message);
}
}
建造者模式
-
建造者模式将客户端与包含多个组成部分的复杂对象的创建过程分离,客户端无需知道复杂对象的内部组成部分与装配方式。它关注如何一步步创建一个复杂对象,不同的具体建造者定义了不同的创建过程,且具体建造者相互独立,增加新的建造者非常方便。
-
建造者模式的优缺点
优点
客户端不必知道产品内部组成的细节,将产品本身与产品的创建过程解耦,使得相同的创建过程可以创建不同的对象。 每一个具体建造者都相互独立,因此可以很方便的替换具体建造者或增加新的具体建造者,用户使用不同的具体建造者就可以得到不同的产品对象。由于指挥者类针对抽象建造者编程,系统扩展方便,符合开闭原则。 可以更加精细地控制产品的创建过程。将复杂产品的创建步骤分解在不同的方法中,使得创建过程更加清晰。
缺点
创建的产品一般具有较多的共同点,其组成部分相似,如果产品之间的差异性很大,例如很多组成部分都不同,不适合使用建造者模式,因此使用范围受到一定的限制。 如果产品的内部变化复杂,可能会导致需要定义很多具体建造者类来实现这种变化,导致系统变得庞大。 建造者模式的应用场景 需要生成的产品对象有复杂的内部结构,这些产品对象通常包含多个成员属性。 需要生成的产品对象的属性相互依赖,需要指定其生成顺序。 对象的创建过程独立与创建该对象的类。在建造者模式中通过引入指挥者类,将创建过程封装在指挥者类中,而不是在建造者类或客户端中。 隔离复杂对象的创建和使用,并使得相同的创建过程可以创建不同的产品。 -
观察者模式可以用一个制作汉堡的例子来解释。
想象一下,我们需要制作一份汉堡,它的组成部分包括面饼、牛肉饼、蔬菜、酱料等。对于每个人来说,他们喜欢的汉堡的组成部分是不同的。有些人可能喜欢多加点生菜,有些人可能喜欢多加点酱料。因此,我们需要一种方法来动态地构建汉堡,以满足不同人的需求。这时候,建造者模式就派上了用场。
// 首先,我们定义一个汉堡类(Burger):
public class Burger {
private String bun;
private String patty;
private List<String> toppings;
private List<String> sauces;
public Burger() {
this.toppings = new ArrayList<>();
this.sauces = new ArrayList<>();
}
public void setBun(String bun) {
this.bun = bun;
}
public void setPatty(String patty) {
this.patty = patty;
}
public void addTopping(String topping) {
this.toppings.add(topping);
}
public void addSauce(String sauce) {
this.sauces.add(sauce);
}
public String describe() {
StringBuilder sb = new StringBuilder("This burger has:\n");
sb.append("- ").append(bun).append(" bun\n");
sb.append("- ").append(patty).append(" patty\n");
sb.append("- Toppings: ");
for (String topping : toppings) {
sb.append(topping).append(", ");
}
sb.setLength(sb.length() - 2);
sb.append("\n- Sauces: ");
for (String sauce : sauces) {
sb.append(sauce).append(", ");
}
sb.setLength(sb.length() - 2);
sb.append("\n");
return sb.toString();
}
}
汉堡类中定义了面饼(bun)、牛肉饼(patty)、蔬菜(toppings)和酱料(sauces)等属性。其中,面饼和牛肉饼是必须的,而蔬菜和酱料是可选的。除此之外,提供了一个describe()
方法,用于描述汉堡的具体组成。
然后,我们定义一个汉堡建造者接口(BurgerBuilder):
public interface BurgerBuilder {
void setBun();
void setPatty();
void addToppings();
void addSauces();
Burger build();
}
汉堡建造者接口中定义了一系列构建汉堡的方法,包括设置面饼、设置牛肉饼、添加蔬菜和添加酱料等。最后,我们还定义了一个build()
方法,用于返回构建好的汉堡对象。
在接口定义好之后,就可以实现不同的汉堡建造者了。下面以蔬菜汉堡建造者(VeggieBurgerBuilder)为例:
public class VeggieBurgerBuilder implements BurgerBuilder {
private Burger burger;
public VeggieBurgerBuilder() {
this.burger = new Burger();
}
@Override
public void setBun() {
burger.setBun("Whole wheat");
}
@Override
public void setPatty() {
burger.setPatty("Veggie patty");
}
@Override
public void addToppings() {
burger.addTopping("Lettuce");
burger.addTopping("Tomato");
burger.addTopping("Onion");
}
@Override
public void addSauces() {
burger.addSauce("Mayo");
burger.addSauce("Ketchup");
}
@Override
public Burger build() {
return burger;
}
}
蔬菜汉堡建造者中实现了汉堡建造者接口中定义的所有方法,并在每个方法中设置了蔬菜汉堡的具体组成。
最后,使用汉堡建造者来构建不同类型的汉堡:
public class Main {
public static void main(String[] args) {
BurgerBuilder veggieBurgerBuilder = new VeggieBurgerBuilder();
veggieBurgerBuilder.setBun();
veggieBurgerBuilder.setPatty();
veggieBurgerBuilder.addToppings();
veggieBurgerBuilder.addSauces();
Burger veggieBurger = veggieBurgerBuilder.build();
System.out.println(veggieBurger.describe());
BurgerBuilder meatLoverBurgerBuilder = new MeatLoverBurgerBuilder();
meatLoverBurgerBuilder.setBun();
meatLoverBurgerBuilder.setPatty();
meatLoverBurgerBuilder.addToppings();
meatLoverBurgerBuilder.addSauces();
Burger meatLoverBurger = meatLoverBurgerBuilder.build();
System.out.println(meatLoverBurger.describe());
}
}
//使用蔬菜汉堡建造者和肉食汉堡建造者(MeatLoverBurgerBuilder)来构建不同类型的汉堡,并打印出它们的具体组成。
// 在上述例子中,无论是使用多个Builder还是单个Builder,都遵循了建造者模式的基本原则。
// 虽然使用多个Builder可以更灵活地构建不同类型的汉堡,但如果你的需求只涉及到一种类型的对象,并且构建过程相对简单,那么使用单个Builder也是合理的。
// 建造者模式可以将复杂对象的构建过程和表示进行解耦,使得同样的构建过程可以创建不同的表示。
// 这种模式适用于需要动态构建复杂对象的场景,同时也能保证对象的构建过程和表示的一致性。
抽象工厂模式
- 抽象工厂模式是一种创建型设计模式,它提供了一种封装一组相关或相互依赖对象的方式,而不需要指定它们的具体类。抽象工厂模式通过一组工厂方法来创建一组产品,这些产品彼此之间有关联或依赖。在抽象工厂模式中,客户端只需要知道接口或抽象类,而不需要知道具体的实现。在运行时,具体的工厂类会创建出具体的产品对象,从而实现了客户端与具体产品之间的解耦。
- 抽象工厂模式通常适用于以下情况:
1.需要创建一组相关或相互依赖的对象。
2.系统独立于它的产品的创建、组合和表示方式。
3.要强调一组相关对象的接口,而不是它们的实现。 - 抽象工厂模式的
优点
包括:
1.封装了产品族的概念,使得代码更加具有可扩展性和可维护性。
2.由于客户端只需要知道抽象类或接口,使得客户端代码可以独立于具体实现变化。 - 抽象工厂模式的
缺点
包括:
1.新增产品族比较麻烦,需要修改抽象工厂的接口,可能会影响到已有代码。
2.新增产品等级结构需要修改所有的工厂类,可能会导致代码的修改量较大。 - 下面使用一个餐厅点餐系统举例
//首先,我们定义一个 Food 接口,该接口包含创建菜品的方法:
public interface Food {
void prepare();
void cook();
void serve();
}
//然后,我们定义一个抽象的菜品工厂接口 FoodFactory,其中包含用于创建不同类型菜品的方法:
public interface FoodFactory {
Food createMainCourse();
Food createSoup();
Food createDessert();
}
//接下来,我们实现具体的菜品类和对应的工厂类。以主食为例:
public class MainCourse implements Food {
public void prepare() {
System.out.println("Preparing main course");
}
public void cook() {
System.out.println("Cooking main course");
}
public void serve() {
System.out.println("Serving main course");
}
}
public class Pasta implements MainCourse {
// 具体的意大利面类实现
}
public class Pizza implements MainCourse {
// 具体的披萨类实现
}
public class Soup implements Food {
public void prepare() {
System.out.println("Preparing soup");
}
public void cook() {
System.out.println("Cooking soup");
}
public void serve() {
System.out.println("Serving soup");
}
}
public class TomatoSoup implements Soup {
// 具体的番茄汤类实现
}
public class MushroomSoup implements Soup {
// 具体的蘑菇汤类实现
}
public class Dessert implements Food {
public void prepare() {
System.out.println("Preparing dessert");
}
public void cook() {
System.out.println("Cooking dessert");
}
public void serve() {
System.out.println("Serving dessert");
}
}
public class Cake implements Dessert {
// 具体的蛋糕类实现
}
public class IceCream implements Dessert {
// 具体的冰淇淋类实现
}
//最后,在客户端中,我们可以根据需要选择特定类型的菜品,并通过工厂类创建对应的菜品对象进行点餐:
public class Client {
private Food mainCourse;
private Food soup;
private Food dessert;
public Client(FoodFactory factory) {
mainCourse = factory.createMainCourse();
soup = factory.createSoup();
dessert = factory.createDessert();
}
public void order() {
mainCourse.prepare();
mainCourse.cook();
mainCourse.serve();
soup.prepare();
soup.cook();
soup.serve();
dessert.prepare();
dessert.cook();
dessert.serve();
}
// 其他操作方法
}
责任链模式
- 责任链模式是一种行为型设计模式,它将请求发送者和接收者解耦,使得多个对象都有机会处理该请求。这些对象组成一条链,按照顺序依次处理请求,直到其中一个对象处理成功或者没有对象可以处理为止。在责任链模式中,每个对象都有一个指向下一个对象的引用,形成了一个链式结构。当一个请求发送给责任链上的某个对象时,该对象会根据自己的条件来判断是否能够处理该请求,如果可以,则处理请求并返回,否则将请求传递给下一个对象。
- 责任链模式的
优点
包括:
1.请求发送者和接收者解耦,降低系统的耦合度。
2.可以动态地增加或修改责任链的处理顺序。
3.可以灵活地给不同的请求设置不同的处理顺序。 - 责任链模式的
缺点
包括:
1.请求可能会被多个对象处理,造成系统性能损失。
2.可能会因为责任链过长而导致系统性能下降。
3.可能会导致请求被无限循环处理。 - 下面使用一个请假审批系统演示:
//首先,我们需要定义一个抽象处理者(Handler)接口,用于处理请假申请,并包含一个设置下一个处理者的方法。
public abstract class Handler {
protected Handler successor;
public void setSuccessor(Handler successor) {
this.successor = successor;
}
public abstract void handleRequest(LeaveRequest request);
}
//然后,我们定义具体的处理者类,比如部门经理(DepartmentManager)、总经理(GeneralManager)和人事部门(HRDepartment)。
public class DepartmentManager extends Handler {
@Override
public void handleRequest(LeaveRequest request) {
if (request.getDays() <= 3) {
// 部门经理审批
System.out.println("部门经理审批通过");
} else if (successor != null) {
successor.handleRequest(request);
}
}
}
public class GeneralManager extends Handler {
@Override
public void handleRequest(LeaveRequest request) {
if (request.getDays() <= 7) {
// 总经理审批
System.out.println("总经理审批通过");
} else if (successor != null) {
successor.handleRequest(request);
}
}
}
public class HRDepartment extends Handler {
@Override
public void handleRequest(LeaveRequest request) {
// 人事部门审批
System.out.println("人事部门审批通过");
}
}
//接下来,我们定义请假申请类(LeaveRequest):
public class LeaveRequest {
private String applicant;
private int days;
public LeaveRequest(String applicant, int days) {
this.applicant = applicant;
this.days = days;
}
public String getApplicant() {
return applicant;
}
public int getDays() {
return days;
}
}
//最后,我们可以使用这些类来实现一个请假审批系统:
public class ChainOfResponsibilityExample {
public static void main(String[] args) {
Handler departmentManager = new DepartmentManager();
Handler generalManager = new GeneralManager();
Handler hrDepartment = new HRDepartment();
departmentManager.setSuccessor(generalManager);
generalManager.setSuccessor(hrDepartment);
LeaveRequest request1 = new LeaveRequest("Alice", 2);
departmentManager.handleRequest(request1); // 部门经理审批通过
LeaveRequest request2 = new LeaveRequest("Bob", 5);
departmentManager.handleRequest(request2); // 总经理审批通过
LeaveRequest request3 = new LeaveRequest("Charlie", 10);
departmentManager.handleRequest(request3); // 人事部门审批通过
}
}
状态模式
- 状态模式是一种行为型设计模式,它允许对象在其内部状态发生改变时改变其行为。状态模式将对象的行为分离成一系列相互独立的状态类,对象在不同的状态下会有不同的行为。
在状态模式中,每个状态都是一个类,而对象通过持有一个状态对象来实现状态的切换。当对象的状态发生改变时,它会委托给当前状态对象来处理相关的操作。 - 状态模式的
优点
包括:
1.将对象的状态和行为分离,使得代码更加易于理解和维护。
2.可以避免使用大量的if-else语句来判断对象的状态,使得代码更加清晰简洁。
3.可以方便地增加新的状态类和对应的行为,扩展性好。 - 状态模式的
缺点
包括:
1.因为每个状态都是一个类,因此可能会导致类的数量增加,增加系统的复杂度。
2.如果状态转换比较复杂或者涉及到多个对象之间的协作,可能需要引入上下文对象来管理状态的切换。 - 下面是用一个订单状态管理来演示:
//首先,我们需要定义表示订单状态的抽象状态类(OrderState),它有一些共享的方法,比如处理订单的方法。
public abstract class OrderState {
protected Order order;
public void setOrder(Order order) {
this.order = order;
}
public abstract void handle();
// 其他共享的方法
}
//然后,我们定义具体的订单状态类,比如新建状态(NewState)、支付中状态(PendingPaymentState)、已支付状态(PaidState)、已发货状态(ShippedState)和已完成状态(CompletedState)。
public class NewState extends OrderState {
@Override
public void handle() {
// 处理新建状态的订单逻辑
}
}
public class PendingPaymentState extends OrderState {
@Override
public void handle() {
// 处理支付中状态的订单逻辑
}
}
public class PaidState extends OrderState {
@Override
public void handle() {
// 处理已支付状态的订单逻辑
}
}
public class ShippedState extends OrderState {
@Override
public void handle() {
// 处理已发货状态的订单逻辑
}
}
public class CompletedState extends OrderState {
@Override
public void handle() {
// 处理已完成状态的订单逻辑
}
}
//接下来,我们定义订单类(Order)并在其中使用状态模式:
public class Order {
private OrderState currentState;
public Order() {
currentState = new NewState();
currentState.setOrder(this);
}
public void setState(OrderState state) {
currentState = state;
currentState.setOrder(this);
}
public void handle() {
currentState.handle();
}
// 其他属性和方法
}
//最后,我们可以使用订单类来管理订单的状态:
public class StatePatternExample {
public static void main(String[] args) {
Order order = new Order();
order.handle(); // 处理新建状态的订单逻辑
order.setState(new PendingPaymentState());
order.handle(); // 处理支付中状态的订单逻辑
order.setState(new PaidState());
order.handle(); // 处理已支付状态的订单逻辑
order.setState(new ShippedState());
order.handle(); // 处理已发货状态的订单逻辑
order.setState(new CompletedState());
order.handle(); // 处理已完成状态的订单逻辑
}
}
装饰器模式
- 装饰器模式(Decorator Pattern)是一种结构型设计模式,它动态地给一个对象添加一些额外的职责,就增加功能来说,装饰器模式相比生成子类更为灵活。
优点
1.灵活性:装饰器模式允许在运行时动态地给对象添加职责,而不需要修改原始类。这使得装饰器模式非常灵活,可以根据需要添加或删除功能。
2.扩展性:通过使用装饰器模式,我们可以将多个装饰器组合在一起,以实现更复杂的功能。这使得装饰器模式非常适合处理可扩展的架构。
3.解耦:装饰器模式将装饰器与原始类解耦,降低了代码之间的耦合度,提高了代码的可维护性和可测试性。缺点
1.代码量增加:使用装饰器模式需要创建多个类和接口,这可能会导致代码量增加。
2.错误使用可能导致混乱:如果不正确地使用装饰器模式,可能会导致代码结构变得混乱和难以理解。- 下面使用咖啡订单系统演示:
//假设我们有一个咖啡店,我们需要实现一个咖啡订单系统。该系统可以根据顾客的选择制作不同种类的咖啡,并且可以根据顾客的要求添加额外的配料。我们将使用装饰器模式来实现这个系统。
//首先,我们定义一个抽象的咖啡接口
public interface Coffee {
String getDescription();
double getCost();
}
//然后,我们创建一个具体的咖啡类,实现咖啡接口:
public class SimpleCoffee implements Coffee {
@Override
public String getDescription() {
return "Simple Coffee";
}
@Override
public double getCost() {
return 1.0;
}
}
//接下来,我们创建一个抽象的装饰器类,实现咖啡接口,并持有一个咖啡对象作为成员变量:
public abstract class CoffeeDecorator implements Coffee {
protected Coffee decoratedCoffee;
public CoffeeDecorator(Coffee coffee) {
this.decoratedCoffee = coffee;
}
@Override
public String getDescription() {
return decoratedCoffee.getDescription();
}
@Override
public double getCost() {
return decoratedCoffee.getCost();
}
}
//然后,我们创建具体的装饰器类,继承装饰器类,并在其中添加额外的配料:
public class MilkDecorator extends CoffeeDecorator {
public MilkDecorator(Coffee coffee) {
super(coffee);
}
@Override
public String getDescription() {
return super.getDescription() + ", with Milk";
}
@Override
public double getCost() {
return super.getCost() + 0.5;
}
}
public class SugarDecorator extends CoffeeDecorator {
public SugarDecorator(Coffee coffee) {
super(coffee);
}
@Override
public String getDescription() {
return super.getDescription() + ", with Sugar";
}
@Override
public double getCost() {
return super.getCost() + 0.3;
}
}
//最后,我们可以在客户端代码中使用装饰器模式来创建咖啡对象,并添加不同的配料:
public class Client {
public static void main(String[] args) {
// 创建一个简单的咖啡对象
Coffee simpleCoffee = new SimpleCoffee();
System.out.println("Description: " + simpleCoffee.getDescription());
System.out.println("Cost: $" + simpleCoffee.getCost());
// 使用装饰器为咖啡对象添加额外的配料
Coffee coffeeWithMilk = new MilkDecorator(simpleCoffee);
System.out.println("Description: " + coffeeWithMilk.getDescription());
System.out.println("Cost: $" + coffeeWithMilk.getCost());
Coffee coffeeWithMilkAndSugar = new SugarDecorator(coffeeWithMilk);
System.out.println("Description: " + coffeeWithMilkAndSugar.getDescription());
System.out.println("Cost: $" + coffeeWithMilkAndSugar.getCost());
}
}
//运行客户端代码,输出如下:
//Description: Simple Coffee
//Cost: $1.0
//Description: Simple Coffee, with Milk
//Cost: $1.5
//Description: Simple Coffee, with Milk, with Sugar
//Cost: $1.8