组合模式
基本介绍
- 组合模式(Composite Pattern),又叫部分整体模式,它创建了对象组的树形结构,将对象组合成树状结构以表示“整体-部分”的层次关系。
- 组合模式依据树形结构来组合对象,用来表示部分以及整体层次。
- 这种类型的设计模式属于结构型模式。
- 组合模式使得用户对单个对象和组合对象的访问具有一致性,即:组合能让客
户以一致的方式处理个别对象以及组合对象
解决的问题
- 组合模式解决这样的问题,当我们的要处理的对象可以生成一颗树形结构,而我们要对树上的节点和叶子进行操作时,它能够提供一致的方式,而不用考虑它是节点还是叶子
- 对应的示意图
![[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-DZ9Bm81V-1643880828541)(F:\StudyNotepad\img\image-20211122183230228.png)]](https://i-blog.csdnimg.cn/blog_migrate/38de1d060bd54afb1c43bfdbaedc55d1.png)
实例演示
现代计算机一般分为具有以下部件:键盘、显示器、机箱、鼠标。而机箱内部包括了主板、硬盘、电源等。主板是主机的心脏,其上面一般又插入了CPU,内存,显卡等设备,请用组合模式描述一台计算机,并尝试根据自己的对游戏的知识,实现一台电脑,能进行“绝地求生”游戏。
分析
我们应该先找到哪些是叶子结点,哪些是根节点。含有其他组件的是根节点(也就是能包含其他组件),最小的节点就是叶子结点,那么它只有最基本的功能。
1)UML类图
![[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-hA6k3IPk-1643880828544)(F:\StudyNotepad\img\image-20211122183510729.png)]](https://i-blog.csdnimg.cn/blog_migrate/18ea0a8fb00f8019e8f7aae9491ec2e4.png)
2)创建父类
不管它是叶子结点还是根节点它本身都是一个组件,只是能够使用的功能不同
/*
* 定义一个基本的组件,不管它其中还包含了什么首先它都是电脑的组件
* */
public abstract class OriginalComponent {
private String name;
private String brand;
public OriginalComponent(String name, String brand) {
this.name = name;
this.brand = brand;
}
/*
* 我们先以最小组件的角度来看,它可以有哪些方法,其他如CPU这些
* 也是一个类,但是当它们继承的这个父类的时候,这些方法的使用具体
* 还要看它们的重写。在父类中默认我们的实现是抛出异常,也就是说
* 如果继承的类不重写这个方法,那么就是没有使用这个方法的权限。
* */
protected void add(OriginalComponent originalComponent){
throw new UnsupportedOperationException();
}
protected void remove(OriginalComponent originalComponent){
throw new UnsupportedOperationException();
}
/*
* 输出组件的信息是基本的功能,应该是每一个组件都有的,那么我们就将这个
* 方法定义为抽象的,每一个继承父类的子类都要实现这个方法。
* */
protected abstract void print();
}
3)根节点
就如同前面所说,根节点和叶子结点的区别在于继承父类以后所能用的功能。
这里就以机箱为例。
/*
* 机箱内部也含有其他组件
* */
public class ComputerCase extends OriginalComponent{
List<OriginalComponent> componentList = new ArrayList<>();
public ComputerCase(String name, String brand) {
super(name, brand);
}
@Override
protected void add(OriginalComponent originalComponent) {
componentList.add(originalComponent);
}
@Override
protected void remove(OriginalComponent originalComponent) {
componentList.remove(originalComponent);
}
@Override
protected void print() {
for (OriginalComponent originalComponent : componentList) {
System.out.println("---机箱---");
originalComponent.print();
}
}
}
4)叶子结点
叶子结点只有最基本的功能,那就是输出信息。
以其中的一个叶子结点CPU为例
public class CPU extends OriginalComponent{
public CPU(String name, String brand) {
super(name, brand);
}
@Override
protected void print() {
System.out.println("部件名称:"+this.getName()+"\t 部件品牌"+getBrand());
}
}
5)组合
将每一个部件包含到管理它的类中。
public class Client {
public static void main(String[] args) {
Computer computer = new Computer("电脑", "pox");
ComputerCase computerCase = new ComputerCase("机箱", "pox");
ComputerMotherboard computerMotherboard = new ComputerMotherboard("主板", "pox");
ComputerKeyboard computerKeyboard = new ComputerKeyboard("键盘", "pox");
Memory memory = new Memory("内存条", "pox");
Application application = new Application("应用", "pox");
Game game = new Game("绝地求生", "蓝洞");
computer.add(computerCase);
computer.add(computerKeyboard);
computerCase.add(computerMotherboard);
computerMotherboard.add(memory);
computer.add(application);
application.add(game);
computer.print();
}
}
6)测试
最后一个问题,实现游戏。我们换一个想法,游戏首先是一个应用,其次它是应用中的一个组件。然而应用也是电脑的组成部分,直接将它游戏添加到组件中即可。
public class Game extends OriginalComponent{
public Game(String name, String brand) {
super(name, brand);
}
@Override
protected void print() {
System.out.println("游戏名称:"+this.getName()+"\t 游戏公司: "+getBrand());
System.out.println("安装完毕~~");
System.out.println("启动游戏....进入战场");
}
}
![[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-sz2S1lB8-1643880828546)(F:\StudyNotepad\img\image-20211122184432580.png)]](https://i-blog.csdnimg.cn/blog_migrate/03f08fa2572816fb17f96525413a0930.png)
小结
不管它包含多少组件,首先它都是这个整体的组成部分。对于包含有其他组件的组件,那么就可以实现更多的方法来包含其他组件(也就是根节点)。对于不含有其他组件的组件就是最基本的组件,那么它只需要实现最基本的功能即可。
补充
关于HashMap,源码也是一种组合模式。
首先是有一个最基本的接口Map(源码中它将我们的例子中的OriginalComponent的一些方法抽取出来创建了一个更加高的接口),在Map中就已经定义了一些如put、putAll等的方法。HashMap并不是直接实现了Map的接口,而是通过继承一个抽象类AbstractMap(就是类似我们这个例子中的OriginalComponent最原生的组件定义),在AbstractMap中实现了一些基本的方法,还有一些如put等的方法,也是默认抛出异常,如果后续继承的类要使用这些方法,那必定只有重写这些方法才能使用。
对于这个组件树中的叶子结点,在HashMap中通过内部类 - Node来实现叶子组件,当进行调用put等方法时,先会将数据包装到这个Node中然后在存放到HashMap中,实现了对组件的管理。
get()方法也是通过查找Node方式来查找。
=====HashMap部分源码,关于put======
public V put(K key, V value) {
return putVal(hash(key), key, value, false, true);
}
=====调用putVal方法存放=====
// 部分源码
final V putVal(int hash, K key, V value, boolean onlyIfAbsent,
boolean evict) {
// 存放到Node中
Node<K,V>[] tab; Node<K,V> p; int n, i;
}
代理模式
基本介绍
- 代理模式:为一个对象提供一个替身,以控制对这个对象的访问。即通过代理对象访问目标对象.这样做的好处是:可以在目标对象实现的基础上,增强额外的功能操作,即扩展目标对象的功能。
- 被代理的对象可以是远程对象、创建开销大的对象或需要安全控制的对象
- 代理模式有不同的形式, 主要有三种 静态代理、动态代理 (JDK代理、接口代理)和 Cglib代理 (可以在内存动态的创建对象,而不需要实现接口, 他是属于动态代理的范畴) 。
静态代理
静态代理在使用时,需要定义接口或者父类,被代理对象(即目标对象)与代理对象一起实现相同的接口或者是继承相同父类 。
实例演示
请使用代理模式实现一个功能:假设要实现一个去哪儿网站,其可以代理航空公司卖机票,也可以代理火车站卖火车票,还可以代理汽车站卖汽车票,分别收取票价的5%,3%, 1%作为代理费。
1)UML类图
![[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-4eiRKke1-1643880921389)(F:\StudyNotepad\img\image-20211122213327890.png)]](https://i-blog.csdnimg.cn/blog_migrate/973d8918b471308cf7d4fd7bf2cb6389.png)
2)创建公共接口
代理的方法,需要相同,那么代理的对象和被代理的对象都要实现相同的接口,这里先定义接口
public interface SellTickets {
void sell();
// 这里getname是为了后续分辨票的类型
String getName();
}
3)创建实现类
定义具体的票的名字和实现买票细节
public class AirTickets implements SellTickets{
private String name = "Air";
@Override
public void sell() {
System.out.println("售卖飞机票~~");
}
@Override
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
}
4)编写动态代理类
同理代理的也是同样的功能。因此也需要实现父类接口
public class GoWhereProxy implements SellTickets{
SellTickets sellTickets;
private String name;
public GoWhereProxy(SellTickets sellTickets) {
name = "去哪儿";
this.sellTickets = sellTickets;
}
/*
* 这里可以根据传入的票的类型,做出相应的功能
* */
@Override
public void sell() {
if (sellTickets.getName().equals("Air")){
System.out.println("飞机票收取5%手续费");
sellTickets.sell();
System.out.println("出票~~");
} else if (sellTickets.getName().equals("Bus")){
System.out.println("汽车票收取1%手续费");
sellTickets.sell();
System.out.println("出票~~");
} else if (sellTickets.getName().equals("Train")){
System.out.println("火车票收取3%手续费");
sellTickets.sell();
System.out.println("出票~~");
}
}
@Override
public String getName() {
return name;
}
}
5)测试
静态代理为了实现售卖多种票的类型,则需要在公共接口中添加一个额外的属性,getName(但对于代理对象来说他实际在这个例子中是不需要这个方法的)。可不可以在不添加额外的方法的情况下实现不同票的代理售卖呢?
动态代理
public class Client {
public static void main(String[] args) {
AirTickets airTickets = new AirTickets();
BusTicket busTicket = new BusTicket();
TrainTicket trainTicket = new TrainTicket();
/*
* 开设三个窗口,每个窗口售卖指定类型的票
* 分别代理飞机、汽车、火车
* */
GoWhereProxy goWhereProxy1 = new GoWhereProxy(airTickets);
goWhereProxy1.sell();
GoWhereProxy goWhereProxy = new GoWhereProxy(busTicket);
goWhereProxy.sell();
GoWhereProxy goWhereProxy2 = new GoWhereProxy(trainTicket);
goWhereProxy2.sell();
}
}
6)结果
![[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-Yw6F3uCS-1643880921391)(F:\StudyNotepad\img\image-20211122214044057.png)]](https://i-blog.csdnimg.cn/blog_migrate/772f010b84b2a26942a1ccb3fd11bca7.png)
小结
优点:在不修改目标对象的功能前提下, 能通过代理对象对目标功能扩展
缺点:因为代理对象需要与目标对象实现一样的接口,所以会有很多代理类
一旦接口增加方法,目标对象与代理对象都要维护
动态代理
基本介绍
- 代理对象,不需要实现接口,但是目标对象要实现接口,否则不能用动态代理
- 代理对象的生成,是利用JDK的API,动态的在内存中构建代理对象
- 动态代理也叫做:JDK代理、接口代理
需要实现Java底层的代理类来实现,根据接收类型来实现不同的代理。
实例测试
1)UML类图
![[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-dVgLAiQh-1643880921392)(F:\StudyNotepad\img\image-20211122215351375.png)]](https://i-blog.csdnimg.cn/blog_migrate/f6813382656445dc2d878ff939f93d3f.png)
2)创建公共接口
和上面的一样也需要定义一个基本接口,所有的售票类型都是售票的一种,将最基本的售票行为抽取出来为一个公共接口。
public interface SellTickets {
void sell();
}
3)实现公共接口
这里以BusTicket为例
public class AirTicket implements SellTickets{
@Override
public void sell() {
System.out.println("售卖飞机票~~");
}
}
4)编写动态代理
底层是通过Java的代理类包来实现,具体的实现方式是通过类的反射来获取类的类型、类中的接口名、类继承的接口等一系列信息。通过获取的信息,来实现不同的传入类型,进行不同的代理。
public class DynamicProxy {
private Object target;
public DynamicProxy(Object target) {
this.target = target;
}
/*
* 这里是在获取动态代理类,这个动态代理类会根据,传入的类的类型来进行代理
* 就可以通过这里将票的类型不同而不同代理方式
* 这里的判断条件就是通过类的反射来获取到类的名称。通过类的名字不同,就可以判断
* 是不同的票的类型。
* */
public Object getProxyInstance(){
return Proxy.newProxyInstance(target.getClass().getClassLoader(),
target.getClass().getInterfaces(),
new InvocationHandler() {
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
System.out.println("代理模式开始~~");
/*
* target.getClass().getName().equals("dynamicproxy.AirTicket")就可以进行判断来做出不同的代理
* 为什么是:dynamicproxy.AirTicket,这个其实可以通过在控制台来打印获取到名称,这里我就是通过
* System.out.println(target.getClass().getName());来在控制台输出获取到类的名称的
* */
/*
* 11.30改进
* - 将获取类的信息改为instance判断
* */
if (target instanceof AirTicket){
System.out.println("收取5%的手续费");
} else if (target instanceof TrainTicket){
System.out.println("收取3%手续费");
} else if (target instanceof BusTicket){
System.out.println("收取1%手续费");
} else {
System.out.println();
}
Object invoke = method.invoke(target, args);
System.out.println("出票~~");
return invoke;
}
});
}
}
小结
就是通过反射来获取不同的代理类型,最后达到不同的代理方式。代理模式在多出源码都有使用,通过代理模式可以实现对方法的增加、扩展等,通常和反射一起使用。
本文详细介绍了组合模式与代理模式。组合模式用于构建对象的树形结构,实现部分与整体的层次关系,使得客户端可以一致地处理单个对象和组合对象。代理模式则为对象提供一个替代品,以控制对这个对象的访问,常用于扩展对象功能。文中通过计算机组件和售票系统的例子,展示了静态代理和动态代理的实现,强调了动态代理在不修改目标对象的情况下扩展功能的优点。

被折叠的 条评论
为什么被折叠?



