设计模式の命令&访问者&迭代器模式
前言
本篇是关于设计模式中命令模式、访问者模式、以及迭代器模式的学习笔记。
一、命令模式
命令模式是一种行为型设计模式
,其核心目的在于将命令的发送者和接受者解耦,提供一个中间层对命令进行统一的管理,请求的发送者不需要知道接收者的具体实现,而只需依赖一个抽象的命令接口。
命令模式通常包括以下的角色:
- 抽象/接口命令层:定义了执行命令的抽象方法,例如执行,撤销。
- 具体命令类:继承/实现了命令层,对命令的具体内容做一个描述。
- 接收者:实际执行操作的对象,包含与请求相关的逻辑。
- 调用者:调用命令对象以触发请求的执行。
- 客户端:创建具体的命令对象,并将其绑定到调用者上。
举一个生活中的案例,假设对于家电,想要通过一个万能遥控器
去控制某个家电的开/关,而不需要每个家电都配一个遥控器,则可以将开,关设为具体命令类:
/**
* 命令抽象类
*/
public interface Command {
/**
* 执行命令
*/
void execute();
/**
* 回滚操作
*/
void undo();
}
以对灯的开关操作为例,开灯的命令类:
/**
* 开灯
*/
public class LightOnCommand implements Command{
private Light light;
public LightOnCommand(Light light) {
this.light = light;
}
@Override
public void execute() {
light.on();
}
@Override
public void undo() {
light.off();
}
}
关灯的命令类:
/**
* 关灯
*/
public class LightOffCommand implements Command{
private Light light;
public LightOffCommand(Light light) {
this.light = light;
}
@Override
public void execute() {
light.off();
}
@Override
public void undo() {
light.on();
}
}
具体的电器,有开关的操作。
public class Light {
public void on(){
System.out.println("把灯打开");
}
public void off(){
System.out.println("关灯了");
}
}
以及一个空的命令类
:
public class NoCommand implements Command{
/**
* 执行命令
*/
@Override
public void execute() {
}
/**
* 回滚操作
*/
@Override
public void undo() {
}
}
命令的统一管理类,主要作用:
- 对命令组进行初始化。
- 设置操控某一个对象的具体命令。
- 执行具体的命令。
- 撤销命令。
public class RemoteController {
//存放各种设备的开命令
Command[] onCommands;
//存放各种设备的关命令
Command[] offCommands;
//撤销的命令
Command unCommand;
/**
* 初始化空命令
*/
public RemoteController(int number) {
onCommands = new Command[number];
offCommands = new Command[number];
for (int i = 0; i < number; i++) {
onCommands[i] = new NoCommand();
offCommands[i] = new NoCommand();
}
}
/**
* 设置具体的命令
*
* @param no 设备组编号
* @param onCommand 具体设备组设备的开命令
* @param offCommand 具体设备组设备的关命令
*/
public void setCommand(int no, Command onCommand, Command offCommand) {
onCommands[no] = onCommand;
offCommands[no] = offCommand;
}
/**
* 按下某个设备的开按钮
*
* @param no 设备组编号
*/
public void onButtonWasPushed(int no) {
onCommands[no].execute();
//记录本次操作,便于撤销
unCommand = onCommands[no];
}
/**
* 按下某个设备的关按钮
*
* @param no 设备组编号
*/
public void offButtonWasPushed(int no) {
offCommands[no].execute();
//记录本次操作,便于撤销
unCommand = offCommands[no];
}
/**
* 撤销
*/
public void undo() {
unCommand.undo();
}
}
客户端:
public class Client {
public static void main(String[] args) {
Light light = new Light();
//初始化开启电灯命令
LightOnCommand lightOnCommand = new LightOnCommand(light);
//初始化关闭电灯命令
LightOffCommand lightOffCommand = new LightOffCommand(light);
//初始化遥控器面板
RemoteController remoteController = new RemoteController(5);
remoteController.setCommand(0, lightOnCommand, lightOffCommand);
System.out.println("--------开启电灯--------");
remoteController.onButtonWasPushed(0);
System.out.println("--------关闭电灯--------");
remoteController.offButtonWasPushed(0);
System.out.println("--------撤销上一步操作--------");
remoteController.undo();
}
}
如果需要进行扩展,比如电视,只需要去增加一个电视机对象
,以及两个实现了命令接口的具体命令类
,最后在客户端进行初始化即可,无需对命令管理类进行修改。
二、访问者模式
访问者模式是一种行为型设计模式
,主要用来将操作与对象结构分离。核心思想在不修改对象结构的前提下,定义作用于这些对象的新操作。
访问者模式通过引入一个访问者对象,将对结构中各个元素的操作从元素类中抽离出来。这样,当需要添加新操作时,无需修改元素类,而只需添加新的访问者类,访问者模式由以下几个角色组成:
- 访问者接口:声明对每种元素类型的访问操作方法。
- 具体访问者:实现访问者接口,定义具体的操作逻辑。
- 元素接口:定义接受访问者对象的方法。
- 具体元素:实现元素接口,在接受访问者对象的方法中调用访问者的相应方法。
- 对象结构:包含一组元素,可以遍历这些元素并让访问者访问它们。
举一个生活中的案例,例如在网上商城将商品加入购物车,需要计算商品总价,还需要获取商品的详细清单,利用访问者模式,可以将商品作为被访问的类
:
/**
* 被访问的元素接口
*/
public interface Item {
void accept(Visitor visitor);
}
/**
* 被访问的元素:书籍
*/
public class Book implements Item {
private String title;
private double price;
public Book(String title, double price) {
this.title = title;
this.price = price;
}
public String getTitle() {
return title;
}
public double getPrice() {
return price;
}
@Override
public void accept(Visitor visitor) {
visitor.visit(this);
}
}
/**
* 被访问的元素:电子产品
*/
public class Electronic implements Item {
private String name;
private double price;
public Electronic(String name, double price) {
this.name = name;
this.price = price;
}
public String getName() {
return name;
}
public double getPrice() {
return price;
}
@Override
public void accept(Visitor visitor) {
visitor.visit(this);
}
}
定义一个访问者接口层
,访问具体的商品(书籍,电子产品):
public interface Visitor {
void visit(Book book);
void visit(Electronic electronic);
}
定义计算价格和打印清单具体的行为:
/**
* 打印清单
*/
public class ItemPrinterVisitor implements Visitor {
@Override
public void visit(Book book) {
System.out.println("Book: " + book.getTitle() + " - Price: " + book.getPrice());
}
@Override
public void visit(Electronic electronic) {
System.out.println("Electronic: " + electronic.getName() + " - Price: " + electronic.getPrice());
}
}
/**
* 计算书籍和电子产品的价格
*/
public class PriceCalculatorVisitor implements Visitor{
private double totalPrice = 0;
@Override
public void visit(Book book) {
totalPrice = totalPrice+book.getPrice();
}
@Override
public void visit(Electronic electronic) {
totalPrice = totalPrice + electronic.getPrice();
}
public double getTotalPrice(){
return totalPrice;
}
}
以及具体的对象接口,让访问者去操作书籍和电子产品:
/**
* 对象接口
*/
public class ShoppingCart {
private List<Item> items = new ArrayList<>();
public void addItem(Item item) {
items.add(item);
}
public void accept(Visitor visitor) {
for (Item item : items) {
item.accept(visitor);
}
}
}
客户端:
public class Client {
public static void main(String[] args) {
// 创建购物车
ShoppingCart cart = new ShoppingCart();
cart.addItem(new Book("Spring源码深度解析", 50));
cart.addItem(new Electronic("Iphone17", 6300));
// 计算总价
PriceCalculatorVisitor priceCalculator = new PriceCalculatorVisitor();
cart.accept(priceCalculator);
System.out.println("Total Price: " + priceCalculator.getTotalPrice());
// 打印清单
ItemPrinterVisitor itemPrinter = new ItemPrinterVisitor();
cart.accept(itemPrinter);
}
}
这样的好处在于,将具体的操作和对象结构分离,后续增加具体的操作,无需对对象结构进行修改。因此适用于需要对对象结构中的对象执行多种不相关的操作,而操作的实现细节需要彼此独立的场景。
同时也可以体会一下如果不使用访问者模式,会存在怎么样的弊端:
- 代码耦合度高:在
ShoppingCart
类中,我们需要判断每个商品的类型,并为不同类型的商品执行不同的操作。这导致了ShoppingCart
类与具体商品类型的紧密耦合。如果要新增一种商品?就又要加一个条件分支。 - 扩展性差:同样地,除了计算价格和打印清单,如果还需要再增加一种操作?那么购物车的代码还需要进行修改。
// 书籍类(Book)
public class Book {
private String title;
private double price;
public Book(String title, double price) {
this.title = title;
this.price = price;
}
public String getTitle() {
return title;
}
public double getPrice() {
return price;
}
}
// 电子产品类(ElectronicProduct)
public class ElectronicProduct {
private String name;
private double price;
public ElectronicProduct(String name, double price) {
this.name = name;
this.price = price;
}
public String getName() {
return name;
}
public double getPrice() {
return price;
}
}
在购物车中直接计算总价,打印清单:
import java.util.ArrayList;
import java.util.List;
public class ShoppingCart {
private List<Object> items;
public ShoppingCart() {
items = new ArrayList<>();
}
public void addItem(Object item) {
items.add(item);
}
public List<Object> getItems() {
return items;
}
// 计算总价
public double calculateTotalPrice() {
double totalPrice = 0;
for (Object item : items) {
if (item instanceof Book) {
totalPrice += ((Book) item).getPrice();
} else if (item instanceof ElectronicProduct) {
totalPrice += ((ElectronicProduct) item).getPrice();
}
}
return totalPrice;
}
// 打印订单清单
public void printOrderDetails() {
System.out.println("Shopping Cart Details:");
for (Object item : items) {
if (item instanceof Book) {
Book book = (Book) item;
System.out.println("Book: " + book.getTitle() + ", Price: " + book.getPrice());
} else if (item instanceof ElectronicProduct) {
ElectronicProduct product = (ElectronicProduct) item;
System.out.println("Product: " + product.getName() + ", Price: " + product.getPrice());
}
}
}
}
三、迭代器模式
迭代器模式是一种行为型设计模式
,核心思想是将集合的遍历操作与集合的实现分离,从而不暴露集合的内部结构,只需要依赖一个公共的接口来访问集合中的元素。通过这种方式,可以在不改变集合结构的情况下,提供不同的遍历方式和逻辑,通常包含以下的角色:
- 迭代器接口:定义遍历集合元素的方法,可以使用JDK的
Iterator
接口。 - 具体迭代器:实现迭代器接口,负责具体的遍历逻辑。
- 聚合接口:定义返回一个迭代器的方法。
- 具体聚合:实现聚合接口,维护集合的实际数据结构,并提供创建迭代器的方法。
假设现在有一个电商项目,需要管理多个订单。每个订单由多个商品组成。希望能够顺序遍历订单中的商品,而不暴露订单内部的实现细节,首先创建一个商品的实例
:
public class Product {
private String productName;
private double price;
public Product(String productName, double price) {
this.productName = productName;
this.price = price;
}
public String getProductName() {
return productName;
}
public double getPrice() {
return price;
}
}
再编写一个聚合接口
:
public interface Aggregate {
Iterator createIterator();
}
创建具体聚合
和迭代器接口
public class Order implements Aggregate{
private String orderId;
private Product[] items;
public Order(String orderId, Product[] items) {
this.orderId = orderId;
this.items = items;
}
public String getOrderId() {
return orderId;
}
public Product[] getItems() {
return items;
}
@Override
public Iterator createIterator() {
return new OrderIterator(this);
}
}
public class OrderIterator implements Iterator {
private Order order;
private int index;
public OrderIterator(Order order) {
this.order = order;
this.index = 0;
}
@Override
public boolean hasNext() {
return index < order.getItems().length;
}
@Override
public Object next() {
if (hasNext()) {
return order.getItems()[index++];
}
return null;
}
}
客户端,创建产品并且遍历:
public class Client {
public static void main(String[] args) {
Product p1 = new Product("产品1", 5);
Product p2 = new Product("产品2", 6);
Product p3 = new Product("产品3", 7);
Order order = new Order("10001", new Product[]{p1, p2, p3});
Iterator iterator = order.createIterator();
while (iterator.hasNext()){
Product next = (Product) iterator.next();
System.out.println(next);
}
}
}
同时也可以对比一下不使用迭代器模式
,在客户端中直接遍历,体会一下存在哪些弊端:
- 客户端直接操作 Order 类的内部实现,如果以后把商品的存储方式改为其他数据结构,所有使用 Order 类的代码都需要修改。
- 遍历商品数组的逻辑直接写在了客户端,每个需要遍历商品的地方,都必须重复写遍历逻辑。
- 以及如果需要进行不同方式的遍历,需要修改遍历逻辑以及所有使用到的客户端的代码。
public class Product {
private String productName;
private double price;
public Product(String productName, double price) {
this.productName = productName;
this.price = price;
}
public String getProductName() {
return productName;
}
public double getPrice() {
return price;
}
}
public class Order {
private String orderId;
private Product[] items;
public Order(String orderId, Product[] items) {
this.orderId = orderId;
this.items = items;
}
public String getOrderId() {
return orderId;
}
public Product[] getItems() {
return items;
}
}
public class Client {
public static void main(String[] args) {
// 创建产品
Product p1 = new Product("Laptop", 1000);
Product p2 = new Product("Smartphone", 800);
Product p3 = new Product("Headphones", 150);
// 创建订单
Product[] products = {p1, p2, p3};
Order order = new Order("O1001", products);
// 直接在客户端遍历商品
Product[] items = order.getItems();
for (int i = 0; i < items.length; i++) {
System.out.println("Product: " + items[i].getProductName() + ", Price: " + items[i].getPrice());
}
}
}
原文地址:https://blog.csdn.net/2301_77599076/article/details/144649520
免责声明:本站文章内容转载自网络资源,如本站内容侵犯了原著者的合法权益,可联系本站删除。更多内容请关注自学内容网(zxcms.com)!