今天的博客主题
设计模式 ——》 设计模式之享元模式
组合模式 CP (Composite Pattern)
定义
将单个对象(叶子节点)和组合对象(树枝节点)用相同的接口表示,使得客户端对单个对象或组合对象的使用一致,感受不到差异。
组合模式一般用来描述整体与部分的关系,它将对象组织到树形结构中,最上面的节点称为根节点,根节点下面可以包含树枝节点和叶子节点,树枝节点下面又可以包含树枝节点和叶子节点。
就像这样
可以看出根节点和树枝节点本质上是同一种数据类型,可以作为容器使用。而树枝节点和叶子节点在语义上不属于一种数据类型,但在组合模式上,会把树枝节点和叶子节点认为是一种数据类型(用同一个接口定义),让他们具备同一样的行为。
这样在组合模式中,整个树形结构中的对象都是同一种数据类型,好处就是客户端无需知道是树枝节点还是叶子节点,都可以直接操作,非常方便。
应用场景
当子系统与其内各个对象的层次呈现树形数据结构时,可使用组合模式让子系统内各个对象层次的行为操作具备一致性。客户端使用该子系统内任意一个层次的对象,无需区分,直接使用通用操作。
1)希望客户端可以忽略组合对象与单个对象的差异时。
2)对象层次具备整体和部分,呈属性结构
生活中常见的组合模式,比如树形菜单,操作系统目录,公司组织架构,文件夹等
优点
1)清楚的定义分层次的复杂对象,表示对象的全部或部分层次
2)客户端忽略了对象层次的差异,方便对整个层次结构进行控制
3)客户端代码简化
4)符合开闭原则
5)
缺点
1)限制类型时比较复杂
2)设计变得更加抽象
3)
源码中的应用
HashMap 中的 putAll() 方法
ArrayList 里的 addAll() 方法
代码示例
组合对象和被组合对象都应该有统一的接口实现或者统一的抽象父类。
组合模式包含3个角色:
1)抽象根节点:定义系统各层次对象的共有方法属性,可预先定义一些默认行为和属性。
2)树枝节点:定义树枝节点的行为,进行存储子节点,组合树枝节点和子节点形成一个树形结构。
3)叶子节点:叶子节点对象,下面没有具体分支,系统层次遍历最小单位。
组合模式在代码具体的实现有两种方式:透明组合模式和安全组合模式。
透明组合模式
透明组合模式是把所有的方法定义在抽象根节点中,这样做的好处是客户端无需分辨是树枝节点和叶子节点,它们具备完全一致的接口。缺点就是叶子节点会继承一些它不需要的方法,与设计原则里的接口隔离原则相违背。
// 场景案例就是 汽车是一个大范围,汽车有各种品牌,每个品牌下推出不同车型不同型号的车
/**
* 定义最上层抽象根节点,把所有可能用到的方法都定义到最顶层的抽象类中,
* 没有任何逻辑代码,为什么不用抽象方法呢?因为用了抽象方法,子类就必须实现,
* 这样就体现不出子类的差异了。这样子类只需要重写差异方法覆盖父类的方法就行了。
*/
abstract class CarComponent{
void addCar(CarComponent catalogCar){
throw new UnsupportedOperationException("不支持上架操作");
}
void removeCar(CarComponent catalogCar){
throw new UnsupportedOperationException("不支持下架操作");
}
String getName(CarComponent catalogCar){
throw new UnsupportedOperationException("不支持获取名称操作");
}
double getPrice(CarComponent catalogCar){
throw new UnsupportedOperationException("不支持获取价格操作");
}
void showInfo(){
throw new UnsupportedOperationException("不支持获取信息操作");
}
}
// 定义树枝节点,也就是汽车品牌
class CarBrand extends CarComponent{
private List<CarComponent> items = new ArrayList<>();
private String name;
private Integer level;
public CarBrand(String name, Integer level) {
this.name = name;
this.level = level;
}
@Override
void addCar(CarComponent catalogCar) {
items.add(catalogCar);
}
@Override
void removeCar(CarComponent catalogCar) {
items.remove(catalogCar);
}
@Override
String getName(CarComponent catalogCar) {
return this.name;
}
@Override
void showInfo() {
System.out.println(this.name);
for (CarComponent carComponent : items){
// 控制显示格式
if(null != this.level){
for (int i = 0; i < this.level; i++) {
System.out.print(" ");
}
for (int i = 0; i < this.level; i++) {
if(i == 0){
System.out.print("+");
}
System.out.print("-");
}
}
carComponent.showInfo();
}
}
}
// 定义叶子节点
class Car extends CarComponent{
private String name;
private double price;
public Car(String name, double price) {
this.name = name;
this.price = price;
}
@Override
String getName(CarComponent catalogCar) {
return this.name;
}
@Override
double getPrice(CarComponent catalogCar) {
return this.price;
}
@Override
void showInfo() {
System.out.println(String.format("%s 现报价 %s¥", this.name, this.price));
}
}
public class CompositePattern {
public static void main(String[] args) {
CarComponent bmwX1 = new Car("bmwX1", 220600.56);
CarComponent bmwX3 = new Car("bmwX3", 310800.99);
CarComponent bmw = new CarBrand("BMW",2);
bmw.addCar(bmwX1);
bmw.addCar(bmwX3);
CarComponent benzE300 = new Car("benzE300", 330800.28);
CarComponent benz = new CarBrand("BenZ",2);
benz.addCar(benzE300);
CarComponent audi = new CarBrand("Audi",2);
CarComponent allCar = new CarBrand("汽车大全",1);
allCar.addCar(bmw);
allCar.addCar(benz);
allCar.addCar(audi);
allCar.showInfo();
}
}
// 输出结果
汽车大全
+-BMW
+--bmwX1 现报价 220600.56¥
+--bmwX3 现报价 310800.99¥
+-BenZ
+--benzE300 现报价 310800.99¥
+-Audi
安全组合模式
安全模式组合是只规定系统各个层次的里最基础的一致行为,就是把组合本身的方法放在自身里。
/**
* 场景案例就是 电脑里的文件夹一个很典型的树形结构
* 目录下有文件夹和文件,文件夹下又可以存放文件夹和文件
* 文件夹就是树枝节点,文件就是叶子节点
* 由于目录系统层次较少,文件夹(树枝节点)较稳定,文件就有很多类型了
* 使用 安全组合模式 实现目录系统
* + D盘
* +-开发工具
* +--eclipse.exe
* +--idea64.exe
* +--editPlus.exe
* +-备份.txt
* +-index.html
*/
// 抽象根目录
abstract class Directory{
protected String name;
public Directory(String name) {
this.name = name;
}
abstract void show();
}
class Folder extends Directory{
private List<Directory> dirs = new ArrayList<>();
private Integer level;
public Folder(String name, Integer level) {
super(name);
this.level = level;
}
public boolean addFile(Directory dir){
return this.dirs.add(dir);
}
public boolean removeFile(Directory dir){
return this.dirs.remove(dir);
}
public Directory getFile(int dir){
return this.dirs.get(dir);
}
public void list(){
for (Directory dir : dirs) {
System.out.println(dir.name);
}
}
@Override
void show() {
System.out.println(this.name);
for (Directory directory : dirs){
// 控制显示格式
if(null != this.level){
for (int i = 0; i < this.level; i++) {
System.out.print(" ");
}
for (int i = 0; i < this.level; i++) {
if(i == 0){
System.out.print("+");
}
System.out.print("-");
}
}
directory.show();
}
}
}
class File extends Directory{
public File(String name) {
super(name);
}
@Override
void show() {
System.out.println(this.name);
}
}
// 客户端调用
public class CompositePatternV2 {
public static void main(String[] args) {
File index = new File("index.html");
File bak = new File("备份.txt");
Folder devTool = new Folder("开发工具", 2);
File eclipse = new File("eclipse.exe");
File idea64 = new File("idea64.exe");
File editPlus = new File("editPlus.exe");
devTool.addFile(eclipse);
devTool.addFile(idea64);
devTool.addFile(editPlus);
Folder dbTool = new Folder("数据库操作工具", 3);
dbTool.addFile(new File("navicat.exe"));
dbTool.addFile(new File("RedisDesktopManager.exe"));
devTool.addFile(dbTool);
Folder d = new Folder("D:", 1);
d.addFile(index);
d.addFile(bak);
d.addFile(devTool);
d.show();
}
}
// 输出结果
D:
+-index.html
+-备份.txt
+-开发工具
+--eclipse.exe
+--idea64.exe
+--editPlus.exe
+--数据库操作工具
+---navicat.exe
+---RedisDesktopManager.exe