JAVA设计模式(篇七)
十一-享元模式
在面向对象程序设计过程中,有时会面临要创建大量相同或相似对象实例的问题。创建那么多的对象将会耗费很多的系统资源,它是系统性能提高的一个瓶颈。例如,围棋和五子棋中的黑白棋子,图像中的坐标点或颜色,局域网中的路由器、交换机和集线器,教室里的桌子和凳子等。这些对象有很多相似的地方,如果能把它们相同的部分提取出来共享,则能节省大量的系统资源,这就是享元模式的产生背景。
1.享元模式的定义与特点
1. 享元模式定义:运用共享技术来有效地支持大量细粒度对象的复用。它通过共享已经存在的对象来大幅度减少需要创建的对象数量、避免大量相似类的开销,从而提高系统资源的利用率。
2. 优点:
- 相同对象只要保存一份,这降低了系统中对象的数量,从而降低了系统中细粒度对象给内存带来的压力。
3. 缺点:
- 为了使对象可以共享,需要将一些不能共享的状态外部化,这将增加程序的复杂性。
- 读取享元模式的外部状态会使得运行时间稍微变长。
2. 模式的结构和实现
享元模式包含两种状态:
内部状态:对象的共同属性,不会随环境的变化而变化
外部状态:对象的非共同属性,会随环境的变化而变化
tips: 享元模式主要就是区分内部状态和外部状态,并将对象的非共同属性外部化
享元模式由,抽象享元角色,具体享元角色,非享元角色,享元工厂角色 组成
1. 模式的结构
- 抽象享元角色: 规范具体享元角色的共同接口,非享元的外部状态以参数的形式通过方法传入。
- 具体享元角色: 实现抽象享元角色中的接口。
- 非享元角色:不可共享的属性,以外部入参方式传入享元角色中。
- 享元工厂角色: 管理具体享元角色的创建和获取,当获取的享元角色不存在 时,先创建一个,再保存返回。
结构图:
2.模式的实现
//非具体享元角色,外部状态
@Data
public class UnSpecialFlyWeight {
//非享元角色信息
private String info;
public UnSpecialFlyWeight(String info){
this.info=info;
}
}
//抽象享元角色
public interface FlyWeight {
//包含非享元角色(外部状态)的抽象享元角色
void outSideMethod(UnSpecialFlyWeight unSpecialFlyWeight);
}
//具体享元角色 内部状态
@Data
public class SpecialFlyWeight implements FlyWeight{
private String key;
public SpecialFlyWeight(String key){
this.key = key;
System.out.println("具体享元角色"+key+"被创建");
}
@Override
public void outSideMethod(UnSpecialFlyWeight unSpecialFlyWeight) {
System.out.println("具体享元角色"+this.key+"的外部信息是:"+unSpecialFlyWeight.getInfo());
}
}
//享元工厂
public class FlyweightFactory {
//通过map保存享元对象
private Map<String,FlyWeight> flyWeightMap = new HashMap<>();
//获取享元对象
public FlyWeight getFlyWeight(String key){
FlyWeight flyWeight = flyWeightMap.get(key);
if(null==flyWeight){
flyWeight = new SpecialFlyWeight(key);
flyWeightMap.put(key,flyWeight);
}
return flyWeight;
}
}
//test
@Test
public void flyweight(){
FlyweightFactory factory = new FlyweightFactory();
FlyWeight one = factory.getFlyWeight("one");
FlyWeight one1 = factory.getFlyWeight("one");
FlyWeight two = factory.getFlyWeight("two");
FlyWeight two1 = factory.getFlyWeight("two");
one.outSideMethod(new UnSpecialFlyWeight("这是第一个对象的信息aaa"));
one1.outSideMethod(new UnSpecialFlyWeight("这是第二个对象的信息bbb"));
two.outSideMethod(new UnSpecialFlyWeight("这是第三个对象的信息ccc"));
two1.outSideMethod(new UnSpecialFlyWeight("这是第四个对象的信息ddd"));
}
//角色one和two只会被创建一次,外部信息每次都不同
3.模式的应用场景
- 系统中存在大量相同或相似的对象,这些对象耗费大量的内存资源。
- 大部分的对象可以按照内部状态进行分组,且可将不同部分外部化,这样每一个组只需保存一个内部状态。
- 由于享元模式需要额外维护一个保存享元的数据结构,所以应当在有足够多的享元实例时才值得使用享元模式。
4.享元模式的扩展
- 单纯享元模式和复合享元模式
十二-组合模式
在现实生活中,存在很多“部分-整体”的关系,例如,大学中的部门与学院、总公司中的部门与分公司、学习用品中的书与书包、生活用品中的衣服与衣柜以及厨房中的锅碗瓢盆等。在软件开发中也是这样,例如,文件系统中的文件与文件夹、窗体程序中的简单控件与容器控件等。对这些简单对象与复合对象的处理,如果用组合模式来实现会很方便。
1.模式的定义与特点
组合模式:有时又叫作将对象组合成树状的层次结构的模式,用来表示“部分-整体”的关系,使用户对单个对象和组合对象具有一致的访问性。
优点:
- 组合模式使得客户端代码可以一致地处理单个对象和组合对象,无须关心自己处理的是单个对象,还是组合对象,这简化了客户端代码;
- 更容易在组合体内加入新的对象,客户端不会因为加入了新的对象而更改源代码,满足“开闭原则”;
缺点:
- 设计较复杂,客户端需要花更多时间理清类之间的层次关系
- 不容易限制容器中的构件
- 不容易用继承的方法来增加构件的新功能
2.模式的结构和实现
1.组合模式的结构
- 抽象构件角色: 主要作用是为树叶构件和树枝构件声明公共接口,并实现它们的默认行为。在透明式的组合模式中抽象构件还声明访问和管理子类的接口;在安全式的组合模式中不声明访问和管理子类的接口,管理工作由树枝构件完成。
- 树叶构件角色: 是组合中的叶节点对象,它没有子节点,用于实现抽象构件角色中声明的公共接口。
- 树枝构件角色: 组合中的分支节点对象,它有子节点。它实现了抽象构件角色中声明的接口,它的主要作用是存储和管理子部件,通常包含Add()、Remove()、GetChild(),和执行操作(operation())等方法。
组合模式分为透明式和安全式:
透明式:在该方式中,由于抽象构件声明了所有子类中的全部方法,所以客户端无须区别树叶对象和树枝对象,对客户端来说是透明的。但其缺点是︰树叶构件本来没有Add()、Remove()及GetChild()方法,却要实现它们(空实现或抛异常),这样会带来一些安全性问题。
安全式:在该方式中,将管理子构件的方法移到树枝构件中,抽象构件和树叶构件没有对子对象的管理方法,这样就避免了上一种方式的安全性问题,但由于叶子组件和分支组件有不同的接口,客户端在调用时要知道树叶对象和树枝对象的存在,所以失去了透明性。其结构图如图2所示。
2.组合模式的实现
透明式
//抽象构件
public interface AbstractComponent {
//添加
void add(AbstractComponent component);
//移除
void remove(AbstractComponent component);
//获取组件
AbstractComponent getChild(int i);
//操作方法
void operation();
}
//树叶组件,本身没有add remove getChild等方法,此处实现空方法
@Data
public class LeafComponent implements AbstractComponent{
private String name;
public LeafComponent(String name){
this.name=name;
}
@Override
public void add(AbstractComponent component) {
}
@Override
public void remove(AbstractComponent component) {
}
@Override
public AbstractComponent getChild(int i) {
return null;
}
@Override
public void operation() {
System.out.println("树叶组件"+name+"被调用");
}
}
//树枝组件
@Data
public class TreeBranchComponent implements AbstractComponent {
private List<AbstractComponent> childList = new ArrayList<>();
@Override
public void add(AbstractComponent component) {
childList.add(component);
}
@Override
public void remove(AbstractComponent component) {
childList.remove(component);
}
@Override
public AbstractComponent getChild(int i) {
return childList.get(i);
}
@Override
public void operation() {
if (!CollectionUtils.isEmpty(childList)) {
childList.forEach(component -> component.operation());
}
}
}
//测试透明式组合模式
@Test
public void combination(){
AbstractComponent pencil = new LeafComponent("铅笔");
AbstractComponent eraser = new LeafComponent("橡皮擦");
AbstractComponent pencilKnife = new LeafComponent("铅笔刀");
AbstractComponent pencilBox = new TreeBranchComponent();
pencilBox.add(pencil);
pencilBox.add(eraser);
pencilBox.add(pencilKnife);
pencilBox.operation();
}
树叶组件铅笔被调用
树叶组件橡皮擦被调用
树叶组件铅笔刀被调用
安全式
//抽象构件==安全
public interface AbstractComponentSafe {
//操作方法
void operation();
}
//树叶组件
@Data
public class LeafComponentSafe implements AbstractComponentSafe{
private String name;
public LeafComponentSafe(String name){
this.name=name;
}
@Override
public void operation() {
System.out.println("树叶组件"+name+"被调用");
}
}
//树枝组件
@Data
public class TreeBranchComponentSafe implements AbstractComponentSafe {
private List<AbstractComponentSafe> childList = new ArrayList<>();
public void add(AbstractComponentSafe component) {
childList.add(component);
}
public void remove(AbstractComponentSafe component) {
childList.remove(component);
}
public AbstractComponentSafe getChild(int i) {
return childList.get(i);
}
@Override
public void operation() {
if (!CollectionUtils.isEmpty(childList)) {
childList.forEach(component -> component.operation());
}
}
}
//测试
@Test
public void combination(){
AbstractComponentSafe pencil = new LeafComponentSafe("铅笔");
AbstractComponentSafe eraser = new LeafComponentSafe("橡皮擦");
AbstractComponentSafe pencilKnife = new LeafComponentSafe("铅笔刀");
//客户端需要知道树枝节点的方法,并使用树枝节点进行方法调用
TreeBranchComponentSafe pencilBox = new TreeBranchComponentSafe();
pencilBox.add(pencil);
pencilBox.add(eraser);
pencilBox.add(pencilKnife);
pencilBox.operation();
}
树叶组件铅笔被调用
树叶组件橡皮擦被调用
树叶组件铅笔刀被调用
3.应用实例
说明: 到某生活用品店购物,用1个红色小袋子装了2包婺源特产(单价7.9元)、1张婺源地图(单价9.9元)﹔用1个白色小袋子装了2包韶关香藉(单价68元)和3包韶关红茶(单价180元)﹔用1个中袋子装了前面的红色小袋子和1个景德镇瓷器(单价380元)﹔用1个大袋子装了前面的中袋子、白色小袋子和1双李宁牌运动鞋(单价198元)。
最后“大袋子"中的内容有:{1双李宁牌运动鞋(单价198元)、白色小袋子{2包韶关香菇(单价68元)、3包韶关红茶(单价180元)}、中袋子{1个景德镇瓷器(单价380
元)、红色小袋子{2包婺源特产(单价7.9元)、1张婺源地图(单价9.9元),现在要求编程显示放在大袋子中的所有商品信息并计算要支付的总价。
//抽象构件==物品
public interface AbstractArticles {
//计算总价
double operation();
}
//树叶构件==商品
@Data
public class GoodsComponent implements AbstractArticles{
//商品名称
private String name;
//商品数量
private int num;
//商品价格
private double price;
public GoodsComponent(String name,int num,double price){
this.name = name;
this.num = num;
this.price = price;
}
//计算价格
@Override
public double operation() {
double totalPrice = this.num * this.price;
System.out.println(this.name+"总价为:"+totalPrice);
return totalPrice;
}
}
//树枝构件==袋子
@Data
public class BagComponent implements AbstractArticles {
private String bagName;
public BagComponent(String bagName) {
this.bagName = bagName;
}
private List<AbstractArticles> childList = new ArrayList<>();
//添加组件
public void add(AbstractArticles componentSafe) {
this.childList.add(componentSafe);
}
@Override
public double operation() {
double totalPrice = 0;
if (!CollectionUtils.isEmpty(this.childList)) {
for (int i = 0; i < this.childList.size(); i++) {
totalPrice += this.childList.get(i).operation();
}
}
System.out.println(this.bagName + "中物品总价为:" + totalPrice);
System.out.println("=========");
return totalPrice;
}
}
@Test
public void combination(){
BagComponent smallRed = new BagComponent("红色小袋子");
AbstractArticles smallRedGoods1 = new GoodsComponent("婺源特产",2,7.9);
AbstractArticles smallRedGoods2 = new GoodsComponent("婺源地图",1,9.9);
smallRed.add(smallRedGoods1);
smallRed.add(smallRedGoods2);
BagComponent smallWhite = new BagComponent("白色小袋子");
AbstractArticles smallWhiteGoods1 = new GoodsComponent("韶关香藉",2,68);
AbstractArticles smallWhiteGoods2 = new GoodsComponent("韶关红茶",3,180);
smallWhite.add(smallWhiteGoods1);
smallWhite.add(smallWhiteGoods2);
BagComponent middleBag = new BagComponent("中袋子");
AbstractArticles middleBagGoods1 = new GoodsComponent("景德镇瓷器",1,380);
middleBag.add(smallRed);
middleBag.add(middleBagGoods1);
BagComponent bigBag = new BagComponent("大袋子");
AbstractArticles bigBagGoods1 = new GoodsComponent("李宁运动鞋",1,198);
bigBag.add(middleBag);
bigBag.add(smallWhite);
bigBag.add(bigBagGoods1);
bigBag.operation();
}
//输出
婺源特产总价为:15.8
婺源地图总价为:9.9
红色小袋子中物品总价为:25.700000000000003
=========
景德镇瓷器总价为:380.0
中袋子中物品总价为:405.7
=========
韶关香藉总价为:136.0
韶关红茶总价为:540.0
白色小袋子中物品总价为:676.0
=========
李宁运动鞋总价为:198.0
大袋子中物品总价为:1279.7
3. 组合模式的应用场景
- 在需要表示一个对象整体与部分的层次结构的场合。
- 要求对用户隐藏组合对象与单个对象的不同,用户可以用统一的接口使用组合结构中的所有对象的场合。
4.组合模式的扩展
如果对前面介绍的组合模式中的树叶节点和树枝节点进行抽象,也就是说树叶节点和树枝节点还有子节点,这时组合模式就扩展成复杂的组合模式了,如Java AWT/Swing中的简单组件JTextComponent有子类JTextField、JTextArea,容器组件Container也有子类Window、Panel。
- 本文标签: 设计模式
- 本文链接: https://www.tianyajuanke.top/article/56
- 版权声明: 本文由吴沛芙原创发布,转载请遵循《署名-非商业性使用-相同方式共享 4.0 国际 (CC BY-NC-SA 4.0)》许可协议授权