这篇接着上篇,结构型模式包含了七种,没有看过上篇的可以点击超链接
Java设计模式之创建型模式
9.适配器模式
9.1结构型模式
Structural Pattern (结构型模式):关注如何将现有类或者对象组织在一起形成更加强大的结构。
结构型模式可以描述两种不同的东西 : 类与类的实例
9.2适配器模式的定义
将一个类的接口转换成客户希望的另一个接口,适配器模式让那些接口不兼容的类可以一起工作
举个例子,我们日常用电都是220V,但是我们可以通过变压器来改变不同的电压,来适配我们的日常电器手机,电脑等。
9.3适配器模式的结构
- Target(目标抽象类):定义客户所需要的接口,可以是个抽象类或者接口,也可以是个具体类
- Adapter(适配器类):它可以调用另一个接口作为一个转换器,Adapter和Target进行适配
- Adaptee(适配者类):适配者即适配角色,它定义了一个已经存在的接口,这个适配类一般是一个具体类
9.4适配器模式的实现
类适配器
适配器类继承了适配者类,实现了目标接口中的方法,直接在类中调用父类的方法转发调用
public class Adapter extends Adaptee implements Target{
public void request(){
super.specificRequest();
}
}
对象适配器
适配器中,引入了适配者对象的引用,然后创建在对其转发调用
public class Adapter extends Target{
private Adaptee adaptee;//维持了对一个适配者对象的引用
public Adapter(Adaptee adaptee){
this.adaptee=adaptee;
}
public void request(){
adaptee.specificRequest();//转发调用
}
}
创建一个引用实例,比如在制造玩具车时,我们要在玩具车移动 的基础上加一些别的功能,比如灯管闪烁,和声音提示
首先创建目标的抽象类,里面也可以实现一些公共的抽象方法
public abstract class CarTarget {
public void move(){
System.out.println("玩具车移动");
}
public abstract void phonate();
public abstract void twinkle();
}
再去分别实现适配者
public class PoliceSound {
public void alarmSound(){
System.out.println("发出警笛声响");
}
}
public class PoliceLamp {
public void alaemLamp(){
System.out.println("出现灯管闪烁");
}
}
再通过适配器将对象实例化
public class PoliceCarAdapter extends CarTarget {
private PoliceLamp policeLamp;
private PoliceSound policeSound;
public PoliceCarAdapter(){
policeLamp =new PoliceLamp();
policeSound =new PoliceSound();
}
@Override
public void phonate() {
policeSound.alarmSound();
}
@Override
public void twinkle() {
policeLamp.alaemLamp();
}
}
测试 ,和之前一样把适配器的方法名写入配置文件中,再利用配置文件xml的工具类进行读取。
public class Client {
public static void main(String[] args) {
CarTarget carTarget =(CarTarget) XMLUtil.getBean(0);
carTarget.move();
carTarget.phonate();
carTarget.twinkle();
}//玩具车移 发出警笛声响 出现灯管闪烁
}
得到了结果,完成了组件的构建。
9.5缺省适配器模式
缺省适配器模式就是当不需要实现接口中所有的方法时,我们可以先设计一个抽象类实现这个借口,对每个方法空实现,后代类只需要继承抽象类,实现想实现的方法即可
- ServiceInterface(适配者接口):是一个接口,在里面声明了大量的方法
- AbstractServiceClass(缺省适配器类):它是缺省适配器模式的核心类,使用空实现了接口中的方法。
- ConcreteServiceClass(具体业务类):它是缺省适配器类的子类,在没有引入适配器之前它需要实现适配者接口
public abstract class AbstractServiceClass implements ServiceInterface{
public void method1(){}
public void method2(){}
public void method3(){}
}
9.6双向适配器
在对象适配器的使用过程中,如果在适配器中同时包含对目标类和适配者类的引用,适配者可以通过目标方法,目标类也可以调用适配者中的方法,那么适配器就是一个双向适配器
public class DoubleAdapter implements Target ,Adaptee{
private Target target ;
private Adaptee adaptee;
public DoubleAdapter(Target target) {
this.target = target;
}
public DoubleAdapter(Adaptee adaptee) {
this.adaptee = adaptee;
}
public void request(){
adaptee.specificRequest();
}
public void specificRequest(){
target.request();
}
}
9.7适配器的优缺点
- 优点:将目标类和适配者类进行解耦,通过引入一个适配器来重用现在的适配者类,无须修改原有结构,增加了类的透明性和复用性,让系统的灵活性和拓展性很好
- 缺点:一次最多适配一个适配者类,不能同时适配多个适配者,主要是在适配器中更换适配者比较麻烦
9.8适配器的适用环境
系统需要创建一些现有类,而这些接口不符合系统的要求,创建一个重复使用的类,和一些相互之间没有太大关联的类一起工作。
10.桥接模式
10.1桥接模式的定义
将抽象部分与它的实现部分解耦,使得两者都能够独立变化
比如蜡笔想要不同画出不同粗细的线条还想拥有不同的颜色,就需要很多种,而毛笔只需要几种型号的笔,和很多的颜料就行,将两个不同维度的变量分离,进行桥接。
10.2桥接模式的结构
- Abstraction(抽象类):定义抽象类的接口,通常是抽象类而不是接口,其中定义了一个Implementtor实现类接口维护该对象
- RefindAbstraction(扩充抽象类):扩充了由Abstraction定义的接口
- Implementor(实现类接口):定义实现类的接口,与Abstraction的接口可以完全不同
- ConcreteImplementor(具体实现类):具体实现了接口,在不同的实现类中提供基本操作的不同实现
10.3桥接模式的实现
如果我们要在不同的操作系统上显示不同格式的文件,这个实例用桥接模式去实现
首先我们得有一个实现类接口,去让操作系统的子类实现
public class Matrix {
//这个是图像像素矩阵
}
public interface ImgeImpl {
public void doPaint(Matrix matrix);//想要显示图像这是最基本的
}
然后得到各种操作系统
public class WindowsImpl implements ImgeImpl {
@Override
public void doPaint(Matrix matrix) {
System.out.println("使用windows显示图像");
}
}
public class LinuxImpl implements ImgeImpl {
@Override
public void doPaint(Matrix matrix) {
System.out.println("使用linux显示图像");
}
}
public class UnixImpl implements ImgeImpl {
@Override
public void doPaint(Matrix matrix) {
System.out.println("使用unix显示图像");
}
}
再者就是抽象的图像类了,在抽象类中必须提供相应的操作系统,要实现相应的方法
public abstract class Image {
protected ImgeImpl imge;
public void setImge(ImgeImpl imge){
this.imge =imge;
}
public abstract void parseFile(String fileName);
}
每种格式都要实现这个抽象类,实现相应的抽象方法
public class GIFImage extends Image {
@Override
public void parseFile(String fileName) {
Matrix matrix =new Matrix();
imge.doPaint(matrix);
System.out.println("图像的格式为GIF,图像的名称为"+fileName);
}
}
public class JPGImage extends Image {
@Override
public void parseFile(String fileName) {
Matrix matrix =new Matrix();
imge.doPaint(matrix);
System.out.println("图像格式为jpg,图像名称为"+fileName);
}
}
public class BMPImage extends Image {
@Override
public void parseFile(String fileName) {
Matrix matrix= new Matrix();
imge.doPaint(matrix);
System.out.println("图像格式为BMP,图像名称为"+fileName);
}
}
public class PGNImage extends Image {
@Override
public void parseFile(String fileName) {
Matrix matrix =new Matrix();
imge.doPaint(matrix);
System.out.println("图像格式为PNG,图像名称为"+fileName);
}
}
同样的我们引入相应的xml类,可以统一的管理很多类名和实例。只需要知道索引就可以得到相应的类
public class TestBridge {
public static void main(String[] args) {
ImgeImpl imge =(ImgeImpl)XMLUtil.getBean(0);
GIFImage gif = (GIFImage)XMLUtil.getBean(1);
gif.setImge(imge);
gif.parseFile("hahaha.gif");
}
}
//使用windows显示图像
//图像的格式为GIF,图像的名称为hahaha.gif
10.4桥接模式的优缺点
- 优点:可以分离抽象接口以及实现部分,比多层继承更好的方案,极大的减少了子类个数,提高了系统的可扩展性
- 缺点:增加了系统的设计难度,很难辨别系统的两个维度的变化
10.5桥接模式的适用环境
需要在抽象化和具体化之间增加更多的灵活性,避免两个层次之间建立静态的继承关系。
11.组合模式
11.1组合模式的定义
组合多个对象形成树形结构以表示具有部分-整体关系的层次结构。组合模式让客户端可以统一对待单个对象和组合对象
就相当于软件中目录结构,当找某一个文件时,所有相关文件夹都会对应的展开。
11.2组合模式的结构
- Component(抽象构件):他可以是接口或者抽象类,为叶子构件和容器构件对象声明接口
- Leaf(叶子构件):在组合结构中表示叶子节点对象,叶子节点没有子节点,它实现了抽象构件中定义的行为
- Composite(容器构件):它在组合结构中表示容器节点对象
11.3组合模式的实现
比如要开发一个扫描杀毒软件,它会从每层目录中挨个扫描,再到每个文件进行扫描杀毒。
我们先要写一个文件类,拥有一切功能,可以是文件夹也可以是文件,如果是文件就不能添加或者移除,只能实现杀毒方法,文件夹应该要对底层每一个文件进行遍历杀毒
public abstract class AbstrcatFile {
public abstract void add(AbstrcatFile file);
public abstract void remove(AbstrcatFile file);
public abstract AbstrcatFile getChild(int i);
public abstract void killVitus();
}
每个文件的实现类
public class VideoFile extends AbstrcatFile {
private String name;
public VideoFile(String name) {
this.name = name;
}
@Override
public void add(AbstrcatFile file) {
}
@Override
public void remove(AbstrcatFile file) {
}
@Override
public AbstrcatFile getChild(int i) {
return null;
}
@Override
public void killVitus() {
System.out.println("对文件"+name+"进行杀毒");
}
}
public class TestFile extends AbstrcatFile {
private String name;
public TestFile(String name) {
this.name = name;
}
@Override
public void add(AbstrcatFile file) {
}
@Override
public void remove(AbstrcatFile file) {
}
@Override
public AbstrcatFile getChild(int i) {
return null;
}
@Override
public void killVitus() {
System.out.println("对文件"+name+"进行杀毒");
}
}
public class ImageFile extends AbstrcatFile {
private String name;
public ImageFile(String name) {
this.name = name;
}
@Override
public void add(AbstrcatFile file) {
}
@Override
public void remove(AbstrcatFile file) {
}
@Override
public AbstrcatFile getChild(int i) {
return null;
}
@Override
public void killVitus() {
System.out.println("对文件"+name+"进行扫描杀毒");
}
}
对文件夹进行实现,在杀毒时要对地下的每层文件也要杀毒遍历,对于增删文件操作也要实现
public class Folder extends AbstrcatFile {
private String name;
private ArrayList<AbstrcatFile> list =new ArrayList<>();
public Folder(String name) {
this.name = name;
}
@Override
public void add(AbstrcatFile file) {
list.add(file);
}
@Override
public void remove(AbstrcatFile file) {
list.remove(file);
}
@Override
public AbstrcatFile getChild(int i) {
return list.get(i);
}
@Override
public void killVitus() {
System.out.println("对文件夹"+name+"进行杀毒");
for (AbstrcatFile obj:list){
obj.killVitus();
}
}
}
测试
public class Testkill {
public static void main(String[] args) {
AbstrcatFile fold1 = new Folder("java资料");
AbstrcatFile fold2 = new Folder("前段资料");
AbstrcatFile fold3 = new Folder("大数据资料");
AbstrcatFile fold4 = new Folder("区块链资料");
AbstrcatFile fold5 = new Folder("资料");
AbstrcatFile file1 = new ImageFile("三上优雅");
AbstrcatFile file2 = new ImageFile("深田咏美");
AbstrcatFile file3 = new VideoFile("小泽玛丽呀");
AbstrcatFile file4 = new VideoFile("斋藤飞鸟");
AbstrcatFile file5 = new TestFile("余罪");
AbstrcatFile file6 = new TestFile("十宗罪");
fold1.add(file1);
fold2.add(file2);
fold2.add(file3);
fold4.add(file4);
fold3.add(file5);
fold1.add(file6);
fold1.add(file6);
fold1.add(file5);
fold4.add(file3);
fold5.add(fold1);
fold5.add(fold2);
fold5.add(fold3);
fold5.add(fold4);
fold5.killVitus();//调用根文件夹,就好对每个文件包括文件夹进行扫描
}
}
11.4透明组合模式,和安全组合模式
- 透明组合模式:抽象构件声明所有的方法,对象都是一致的。
这个有不好的地方,文件对其进行空实现,但是在调用时要注意,不能被调用,要提供相应的异常抛出 - 安全组合模式:抽象构件对文件不提供管理成员的方法,就可以避免被调用。
缺点就是不能完全的面对抽象编程,要对其进行区分。
11.5组合模式的优缺点
- 优点:可以很清楚的定义分层次的复杂对象,表示全部或者部分层次,让客户端忽略层次差异,方便对整个层次进行控制。
- 缺点:新增加构件很难对容器中的构件类型进行限制
11.6组合模式的适用环境
具有整体和部分层次结构中希望以一种方式忽略整体与部分的差异,客户端可以一致的对待他们。
12.装饰模式
12.1装饰模式的定义
动态地给一个对象增加一些额外的职责。就扩展功能而言,装饰模式提供了一种比使用子类更加灵活的替代方案。
比如在照片类的基础上做好照片好,将照片放入一个框中,对照片进行了装饰,对原有功能进行拓展
12.2装饰模式的结构
- Component(抽象构件):它是具体构件和抽象构件的共同父类,声明了在具体构件中实现的业务方法。
- ConcreteComponent(抽象装饰类):它是抽象构件类的子类,用于具体的构件对象,实现了在抽象构件中的方法,装饰类可以给它增加额外的职责
- Decorator(抽象装饰类):它也是抽象构件类的子类,用于给具体构件类怎加职责
- ConcreteDecorator(具体装饰类):它是抽象装饰类的子类,负责向构件添加新的职责
12.3装饰类的实现
比如我要在图形界面上显示不同的框体,在每个框体上我还可以增加修饰类,去让这个框体更加漂亮
首先我得先写一个抽象构件
public abstract class Component {
public abstract void display();
}
再去实现抽象构件的基本功能
public class Window extends Component {
@Override
public void display() {
System.out.println("显示窗体");
}
}
public class TextBox extends Component {
@Override
public void display() {
System.out.println("显示文本框");
}
}
public class ListBox extends Component {
@Override
public void display() {
System.out.println("显示列表");
}
}
建立一个抽象装饰类,将基本实现类注入
public class ComponentDecorator extends Component {
private Component component;
public ComponentDecorator (Component component){
this.component=component;
}
@Override
public void display() {
component.display();
}
}
建立具体的装饰类,继承抽象装饰类,增加新增加的装饰方法
public class ScorllbarDecorator extends ComponentDecorator {
public ScorllbarDecorator(Component component) {
super(component);
}
public void display(){
super.display();
this.setScorllBar();
}
public void setScorllBar(){
System.out.println("增加滚动条");
}
}
public class BlackBorlderDecorator extends ComponentDecorator {
public BlackBorlderDecorator(Component component) {
super(component);
}
public void display(){
super.display();
this.setBlackBorlder();
}
public void setBlackBorlder(){
System.out.println("为构件增加黑色边框");
}
}
测试对象
public class DecoratorTest {
public static void main(String[] args) {
Component component = new ListBox();
ComponentDecorator decorator = new ScorllbarDecorator(component);
//decorator.display();
ComponentDecorator decorator1 = new BlackBorlderDecorator(decorator);
decorator1.display();
}
}
//显示列表 增加滚动条 为构件增加黑色边框
可以多层进行修饰。
12.5透明装饰模式与半透明模式装饰
透明装饰模式:就是客户端完全面向抽象编程,对于所有构件对象和具体装饰类都为同一一个抽象声明。如下
Component component = new ListBox();
Component decorator = new ScorllbarDecorator(component);
//decorator.display();
Component decorator1 = new BlackBorlderDecorator(decorator);
decorator1.display();
使用起来很一致,很方便都是同一个对象声明,无须关心他们之间的区别。
半透明装饰模式:就是在装饰声明的时候用子类的直接实现声明,这样可以单独调用新增的装饰方法。会给系统带来更大的灵活性。
Component component = new ListBox();
ComponentDecorator decorator = new ScorllbarDecorator(component);
decorator.setScorllbar();
ComponentDecorator decorator1 = new BlackBorlderDecorator(decorator);
decorator1.display();
decorator1.setBlackBorlder();
12.6 装饰模式的优缺点
- 优点:扩展功能时比继承更加灵活,不会导致类的急剧增加,动态的扩展一个类的对象功能,可以对一个对象进行多次装饰,具体装饰和具体构件之间可以独立变化。
- 缺点:使用装饰模式设计时会产生很多小对象,装饰模式比继承模式更容易出错,对于多次装饰的对象,在排查错误时更加困难。
12.7装饰模式的适用环境
在不影响其他对象的情况下以动态透明的方式给对象添加职责,当不能采用继承的方式给系统进行扩展和维护。
13外观模式
13.1外观模式的定义
为了子系统中的一组接口提供一个统一的入口。外观模式定义了一个高层接口,这个接口使得这一子系统更加容易使用。
13.2外观模式的结构
- Facade(外观角色):在客户端可以调用它的方法,外观角色中可以知道相关子系统的功能和职责
- SubSystem(子系统角色):在软件系统中可以有一个或多个子系统角色,可以不是一个单独的类,而是一个类的集合
13.3外观系统的实现
先写一个读取文件的类
public class FileReader {
public String read(String fileNameSrc){
System.out.println("读取文件,获取明文:");
StringBuffer stringBuffer = new StringBuffer();
try {
FileInputStream fis = new FileInputStream(fileNameSrc);
int data;
while((data = fis.read())!=-1){
stringBuffer = stringBuffer.append((char) data);
}
fis.close();
System.out.println(stringBuffer.toString());
} catch (FileNotFoundException e) {
e.printStackTrace();
} catch (IOException e) {
e.printStackTrace();
}
return stringBuffer.toString();
}
}
再写一个对文件加密的类
public class CipherMachine {
public String encrypt(String plainText){
System.out.println("数据加密,将明文转换为密文");
String res = "";
for (int i = 0; i <plainText.length() ; i++) {
String c = String.valueOf(plainText.charAt(i)%7);
res += c;
}
System.out.println(res);
return res;
}
}
写一个写入文件的类
public class FileWriter {
public void write(String encyptSrc , String fileName){
System.out.println("将密文写入文件");
try {
FileOutputStream fos = new FileOutputStream(fileName);
fos.write(encyptSrc.getBytes());
fos.close();
} catch (FileNotFoundException e) {
e.printStackTrace();
} catch (IOException e) {
e.printStackTrace();
}
}
}
写一个外观类调用串一起的类
public class EncryptFacade {
private FileReader fileReader;
private CipherMachine cipherMachine;
private FileWriter fileWriter;
public EncryptFacade (){
fileReader = new FileReader();
cipherMachine = new CipherMachine();
fileWriter = new FileWriter();
}
public void fileEncrypt(String fileNameSrc,String fileNameDec){
String plain = fileReader.read(fileNameSrc);
String encrypt = cipherMachine.encrypt(plain);
fileWriter.write(encrypt,fileNameDec);
}
}
完成文件的读取加密,另存为写入的过程。
13.4抽象外观类
对于上一个例子中,最后一个外观类,对客户端提供了极大的便利,但是如果想修改某一个子系统,就变得很麻烦了,所以我们引入了外观抽象类,可以不用修改源码而达到更换外观的目的。
public abstract class AbstractEncryptFacade {
public abstract void fileEncrypt(String fileNameSrc,String fileNameDec);
}
public class NewEncryptFacade extends AbstractEncryptFacade {
private FileReader fileReader;
private CipherMachine cipherMachine;
private FileWriter fileWriter;
public NewEncryptFacade (){
fileReader = new FileReader();
cipherMachine = new CipherMachine();
fileWriter = new FileWriter();
}
public void fileEncrypt(String fileNameSrc,String fileNameDec){
String plain = fileReader.read(fileNameSrc);
String encrypt = cipherMachine.encrypt(plain);
fileWriter.write(encrypt,fileNameDec);
}
}
这样极大的增强了系统的拓展能力
13.5外观模式的优缺点
- 优点:对客户端屏蔽了子系统的组件,减少了客户端所需要处理的对象数目,让子系统使用起来更加方便
- 缺点:不能很好的限制客户端直接使用子系统类,如果客户端访问子系统类做太多的限制则减少了可变性和灵活性。
13.6外观模式的适用环境
为访问一系列复杂的子系统提供统一的简单入口,客户端程序与多个子系统之间存在很大的依赖性。
14.享元模式
14.1享元模式的定义
运用共享救赎有效的支持大量细粒度对象的复用
享元实例,在享元池中的唯一的,但是在外部是有不同的状态
储存共享实例对象的地方被称为享元池
就好比一个字符串串中的重复字母,如果把每个字符都单独来看会占用很多空间,使用共享将这些综合起来。
像极了古代的活字印刷术。
14.2享元模式的结构
- Flyweight(抽象享元类):通常是一个接口或者抽象类
- ConcreteFlyweight(具体享元类):实现了抽象享元类
- UnsharedConcreteFlyweight(非共享具体享元类):并不是所有的享元类子类都被共享
- FlyweightFactory(享元工厂类):创建并管理享元对象,提供享元池
14.3享元模式的实现
比如我们在下围棋的时候,黑白棋就是享元类。
首先我们创建享元抽象类
public abstract class IgoChessman {
public abstract String getColor();
public void display(){
System.out.println("棋子颜色:"+this.getColor());
}
}
分别提供两个子类
public class BlackIgoChessman extends IgoChessman {
@Override
public String getColor() {
return "黑色";
}
}
public class WriteIgoChessman extends IgoChessman {
@Override
public String getColor() {
return "白色";
}
}
再写享元工厂类,用单例模式对其设计
public class IgoChessFactory {
private static IgoChessFactory instance = new IgoChessFactory();
private static Hashtable hashtable;
private IgoChessFactory(){
hashtable = new Hashtable();
IgoChessman black = new BlackIgoChessman();
hashtable.put("b",black);
IgoChessman write = new WriteIgoChessman();
hashtable.put("w",write);
}
public static IgoChessFactory getInstance(){
return instance;
}
public static IgoChessman getIgoChessman(String color){
return (IgoChessman) hashtable.get(color);
}
}
测试
public class Client {
public static void main(String[] args) {
IgoChessFactory factory = IgoChessFactory.getInstance();
IgoChessman b1 = IgoChessFactory.getIgoChessman("b");
IgoChessman b2 = IgoChessFactory.getIgoChessman("b");
IgoChessman b3 = IgoChessFactory.getIgoChessman("b");
System.out.println("黑子是否相同:"+(b1==b2));//ture
System.out.println("黑子是否相同:"+(b2==b3));//true
IgoChessman w1 = IgoChessFactory.getIgoChessman("w");
IgoChessman w2 = IgoChessFactory.getIgoChessman("w");
System.out.println("黑子是否相同:"+(w1==w2));//true
b1.display();//黑色
b2.display();//黑色
b3.display();//黑色
w1.display();//白色
w2.display();//白色
}
}
14.4有外部状态的享元模式
在棋牌上两个黑子本质是一样的,但是外部状态位置是不一样的,我们可以新加一个类Coordinates,来给棋子添加坐标。
public class Coordinates {
private int x ;
private int y ;
public Coordinates(int x, int y) {
this.x = x;
this.y = y;
}
public int getX() {
return x;
}
public void setX(int x) {
this.x = x;
}
public int getY() {
return y;
}
public void setY(int y) {
this.y = y;
}
}
public abstract class IgoChessman {
public abstract String getColor();
public void display(Coordinates coord){
System.out.println("棋子颜色:"+this.getColor()+"棋子位置:("
+coord.getX()+","+coord.getY()+")");
}
}
public class Client {
public static void main(String[] args) {
IgoChessFactory factory = IgoChessFactory.getInstance();
IgoChessman b1 = IgoChessFactory.getIgoChessman("b");
IgoChessman b2 = IgoChessFactory.getIgoChessman("b");
IgoChessman b3 = IgoChessFactory.getIgoChessman("b");
System.out.println("黑子是否相同:"+(b1==b2));
System.out.println("黑子是否相同:"+(b2==b3));
IgoChessman w1 = IgoChessFactory.getIgoChessman("w");
IgoChessman w2 = IgoChessFactory.getIgoChessman("w");
System.out.println("黑子是否相同:"+(w1==w2));
b1.display(new Coordinates(1,3));
b2.display(new Coordinates(2,4));
b3.display(new Coordinates(5,3));
w1.display(new Coordinates(8,6));
w2.display(new Coordinates(9,7));
/*黑子是否相同:true
黑子是否相同:true
黑子是否相同:true
棋子颜色:黑色棋子位置:(1,3)
棋子颜色:黑色棋子位置:(2,4)
棋子颜色:黑色棋子位置:(5,3)
棋子颜色:白色棋子位置:(8,6)
棋子颜色:白色棋子位置:(9,7)
*/
}
}
14.5单纯享元模式和复合享元模式
- 单纯享元模式:单纯享元模式所有具体的享元类都是可以共享的,不存在非共享具体享元类,
- 复合享元模式:将一些单纯的享元对象使用组合模式加以组合还可以形成复合享元对象,这样的复合享元对象本身不能共享,但是他们可分解成单纯享元对象,而后者可以共享。
14.6享元模式与String类
JDK中的String类就采用了享元模式,享元池,就和字符串常量池一样。
14.7享元模式的优缺点
- 优点:极大的减少内存中对象的数量,使得相同或者相似的对象在内存中只保留一份,从而节约系统资源,提高系统性能
- 缺点:使系统变得复杂,需要分离出内部状态和外部状态,使得程序逻辑变得复杂。
14.8 享元模式适用环境
一个系统有大量相同或者相似的对象,造成内存的大量耗费,对象大部分都可以外部化,可以将这些外部状态传入对象中,需要多次适用享元对象。
15 代理模式
15.1 代理模式的定义
给某一个对象提供一个代理或站位符,并由代理对象来控制对原对象的访问。
就相当于代购一样,用户只关心商品,而代购做剩下的事情。
代理模式是一种结构型模式。在代理模式中引入一个新代理对象,代理对象在客户端和对象目标之间起到中介作用,它去掉客户不能看到的内容和服务或者增添客户需要的额外的新服务。
15.2代理模式的结构
- Subject(抽象主题角色):声明了真实主题和代理主题的共同接口
- Proxy(代理主题角色):包含了对真实主题的引用,可以在任何时候操作主题对象
- RealSubject(真实主题角色):定义了代理对象所代表的真实对象
15.3常见的代理模式
- 远程代理:为一个位于不同地址空间的对象提供一个本地的代理对象,这个不同的地址空间可以在同一台主机,也可以在另一台主机中,远程代理又被称为大使
- 虚拟代理:如果需要创建一个资源消耗较大的对象,先创建一个资源相对较小的对象来表示,真实对象只在需要时才会被真正创建
- 保护代理:控制对一个对象的访问,可以给不同用户不同等级的使用权限
- 缓冲代理:为某一个目标操作的结果提供临时的存储空间,以便多个客户端可以共享这个结果
- 智能引用代理:当一个对象被引用时提供一些额外的操作,例如对象调用次数
15.4代理模式的实现
当我们在登录是需要对象信息进行验证,并加访问日志,用户访问时,用户不会调用方法,所以需要一个代理做这些事情,用户只关心业务层就好。
首先先写验证和事务
public class AccessValidator {
public boolean validator(String userId){
System.out.println("验证用户"+userId+"是否合法");
if(userId.equals("9587")){
System.out.println(userId+"登录成功");
return true;
}else {
System.out.println(userId+"登录失败");
return false;
}
}
}
public class Logger {
public void log(String userId){
System.out.println("更新数据库"+userId+"查询次数+1");
}
}
写主要业务的接口
public interface Searcher {
public String doSearcher(String userId , String password);
}
核心业务的实现类
public class RealSearcher implements Searcher {
@Override
public String doSearcher(String userId, String keyword) {
System.out.println("用户"+userId+"使用关键词"+keyword+"查询相关业务");
return "返回具体内容";
}
}
代理去实现业务,然后帮忙做剩下的事
public class ProxySearcher implements Searcher {
private RealSearcher searcher = new RealSearcher();
private AccessValidator validator ;
private Logger logger;
@Override
public String doSearcher(String userId, String password) {
if(this.validator(userId)){
String res = searcher.doSearcher(userId,password);
this.log(userId);
return res;
}else {
return null;
}
}
public boolean validator(String userId){
validator = new AccessValidator();
return validator.validator(userId);
}
public void log(String userId){
logger= new Logger();
logger.log(userId);
}
}
我们之前一样把代理的类名写入到xml配置中,利用xml工具类来获取这个代理类,进行测试
public class ProxyTest {
public static void main(String[] args) {
Searcher proxy = (Searcher) XMLUtil.getBean(0);
proxy.doSearcher("9587","258");
}
}
15.5 Java的动态代理
在JDK中的proxy类中提供了一个静态方法
public static Object newProxyInstance(ClassLoader loader,
Class<?>[] interfaces,
InvocationHandler h)
其中要提供一个InvaocationHandler接口的实现类,这个实现类就是代理对象
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable;
接口中需要重新这个方法,我们要实现动态代理就得实现这个接口,完成代理对象。
public interface AbstractDocumentDao {
public boolean deleteDocumentById(String userId);
}
public class UserDao implements AbstractUserDao {
@Override
public Boolean findUserById(String userId) {
if(userId.equals("520")){
System.out.println("查询ID为"+userId+"的用户登录成功");
return true;
}else {
System.out.println("查询ID为"+userId+"的用户登录失败");
return false;
}
}
}
public class DocumentDao implements AbstractDocumentDao {
@Override
public boolean deleteDocumentById(String userId) {
if(userId.equals("D001")){
System.out.println("删除id为"+userId+"的文档信息成功");
return true;
}else {
System.out.println("删除id为"+userId+"的文档信息失败");
return false;
}
}
}
public class DaoLogHandler implements InvocationHandler {
private Calendar calendar;
private Object object;
public DaoLogHandler() {
}
public DaoLogHandler(Object object) {
this.object = object;
}
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
beforeInvoke();
Object invoke = method.invoke(object, args);
afterInvoke();
return invoke;
}
public void beforeInvoke(){
calendar = new GregorianCalendar();
int hour = calendar.get(Calendar.HOUR_OF_DAY);
int minute = calendar.get(Calendar.MINUTE);
int second = calendar.get(Calendar.SECOND);
String time = hour+":"+minute+":"+second;
System.out.println("调用时间为:"+time);
}
public void afterInvoke(){
System.out.println("方法调用结束!");
}
}
public class Client {
public static void main(String[] args) {
InvocationHandler handler = null;
AbstractUserDao userDao = new UserDao();
handler = new DaoLogHandler(userDao);
AbstractUserDao proxy = null;
//这个传入抽象类的class的类加载器对,但是实例对象接口有问题有问题
//proxy = (AbstractUserDao)Proxy.newProxyInstance(AbstractUserDao.class.getClassLoader(),AbstractUserDao.class.getInterfaces(),handler);
//这个传入的是实例对象的类加载器和实例接口,这个对
//proxy =(AbstractUserDao) Proxy.newProxyInstance(userDao.getClass().getClassLoader(), userDao.getClass().getInterfaces(), handler);
//这个利用了内部类对接口实现
proxy = (AbstractUserDao) Proxy.newProxyInstance(AbstractUserDao.class.getClassLoader(),new Class[]{AbstractUserDao.class},handler);
proxy.findUserById("520");
System.out.println("===================================");
AbstractDocumentDao documentDao = new DocumentDao();
handler = new DaoLogHandler(documentDao);
AbstractDocumentDao proxy2 = null;
proxy2 = (AbstractDocumentDao) Proxy.newProxyInstance(AbstractDocumentDao.class.getClassLoader(),new Class[]{AbstractDocumentDao.class},handler);
proxy2.deleteDocumentById("D001");
//调用时间为:10:0:6
//查询ID为520的用户登录成功
//方法调用结束!
//===================================
//调用时间为:10:0:6
//删除id为D001的文档信息成功
//方法调用结束!
}
}
15.5代理模式 的优缺点
- 优点:协调调用者和被调用者,在一定程度上降低了系统的耦合度,系统具有较好的灵活性和可扩展性,客户端可以面向抽象主题进行编程,无须修改源码
- 缺点:代理模式可能造成请求处理变慢,实现代理需要一些额外的工作,实现过程较为复杂
15.6代理模式的适用环境
当客户端远程访问主机对象,使用远程代理
需要一个消耗资源较少的对象来代表一个消耗资源较多的对象,从而降低系统的开销,缩短运行时间,就需要一个虚拟代理
需要频繁访问操作结果提供一个临时存储空间,以便于多个客户端共享这些结果,需要一个缓冲代理
控制对一个对象的访问,为不同的对象提供不同级别的访问权限可以使用保护代理