文章目录
1 适配器模式
1.1 认识适配器
如果你想给你的手机充电,但是附近只有一个三孔插座,为此你可能需要一个插线板
这个插线板就是适配器,而OO适配器和真实世界的适配器扮演着同样的角色:将一个接口转换成另一个接口,以符合客户的期望
我们来看看JDK中常用的适配器模式:
在swing技术中,我们通常要为各种组件增加侦听器,如焦点的获取/失去:
/*编号文本框获取焦点,编号文本框被全部选中*/
this.jtxtId.addFocusListener(new FocusAdapter() {
@Override
public void focusGained(FocusEvent e) {
jtxtId.selectAll();
}
});
/*名称文本框获取焦点,名称文本框被全部选中*/
this.jtxtName.addFocusListener(new FocusAdapter() {
@Override
public void focusGained(FocusEvent e) {
jtxtName.selectAll();
}
});
打开FocusAdapte类:
打开FocusListener接口:
这就是适配器模式,FocusAdapter就是一个适配器,它实现了FocusListener接口
这里FocusAdapter实现了FocusListener,但是对于要完成的两个方法,选择了什么都不做,空适配器是合理的,这里将具体的实现方式交给用户,让用户选择重写FocusAdapter中的方法来使用
1.2 更换电视机
先来看一个简单的例子:
/**
* @author 雫
* @date 2021/3/5 - 9:25
* @function 电视机
*/
public class Tv {
public Tv() {}
public void on() {
System.out.println("关闭电视");
}
public void off() {
System.out.println("打开电视");
}
}
/**
* @author 雫
* @date 2021/3/5 - 8:19
* @function 用户
*/
public class User {
public static void main(String[] args) {
Tv tv = new Tv();
tv.on();
tv.off();
}
}
这个程序简单模拟用户使用电视机的过程,但是问题也很明显:
1,实现与功能捆绑,面向实现编程,而不是面向接口编程
2,存在对象依赖,应该依赖抽象,而不是具体类
我们用适配器模式扩展它的功能,并实现解耦:
/**
* @author 雫
* @date 2021/3/5 - 10:28
* @function 电视
*/
public class Tv {
public Tv() {}
public void on() {
System.out.println("打开电视");
}
public void off() {
System.out.println("关闭电视");
}
public void watchMovie() {
System.out.println("打开观影功能");
}
public void listenMusic() {
System.out.println("打开音乐功能");
}
public void playGame() {
System.out.println("打开游戏功能");
}
}
/**
* @author 雫
* @date 2021/3/5 - 9:30
* @function 电视功能接口
*/
public interface TvFunction {
void on();
void off();
void watchMovie();
void listenMusic();
void playGame();
}
/**
* @author 雫
* @date 2021/3/5 - 9:31
* @function 普通电视接口
*/
public class TvFunctionAdapter implements TvFunction {
private Tv tv;
public TvFunctionAdapter() {
this.tv = new Tv();
}
@Override
public void on() {
tv.on();
}
@Override
public void off() {
tv.off();
}
@Override
public void watchMovie() {
System.out.println("请购买会员后享使用电影服务");
}
@Override
public void listenMusic() {
System.out.println("请购买会员后使用音乐服务");
}
@Override
public void playGame() {
System.out.println("请购买会员后使用游戏服务");
}
}
/**
* @author 雫
* @date 2021/3/5 - 9:32
* @function Vip电视接口
*/
public class TvFunctionVipAdapter implements TvFunction {
private Tv tv;
public TvFunctionVipAdapter() {
this.tv = new Tv();
}
@Override
public void on() {
tv.on();
}
@Override
public void off() {
tv.off();
}
@Override
public void watchMovie() {
tv.watchMovie();
}
@Override
public void listenMusic() {
tv.listenMusic();
}
@Override
public void playGame() {
tv.playGame();
}
}
/**
* @author 雫
* @date 2021/3/5 - 8:19
* @function 用户
*/
public class User {
private TvFunction tv;
public User(TvFunction tv) {
this.tv = tv;
}
public void setTv(TvFunction tv) {
this.tv = tv;
}
public void openTv() {
this.tv.on();
}
public void closeTv() {
this.tv.off();
}
public void watchMovie() {
this.tv.watchMovie();
}
public void ListenMusic() {
this.tv.listenMusic();
}
public void platGame() {
this.tv.playGame();
}
}
测试:
通过使用适配器模式,我们将功能与实现解耦,让User类只依赖抽象,如果想要更改功能,只需要更改适配器即可
并且这里通过组合,将TvFunction作为User的成员,扩展了User的功能,还提供了setTv()的方法,可以让用户替换适配器
1.3 适配器模式
适配器模式:
将一个类的接口,转换成客户期望的另一个接口
适配器让原本接口不兼容的类可以轻松合作
1.2代码的工作方式:
1.2代码中:
Tv: 被适配者
TvFunctionVipAdapter/TvFunctionAdapter: 适配器
TvFunction: 目标接口
User: 客户
在客户中调用的方法,实际上是调用适配器中的方法
而适配器可以委托被适配者完成该方法,也可以自己完成该方法
1.4 枚举器与迭代器
在早期Java的集合类型(Vector,Stack,Hashtable)都实现了一个名为elements()的方法,该方法会返回一个枚举(Enumeration),这个Enumeration接口可以逐一遍历集合中的每个元素,而无需知道它们在几个中是如何被管理的
Enumeration中有两个方法,一个判断是否还有下一个元素,一个取得集合中的下一个元素
当sun推出更新的集合后,开始使用迭代器(Iterator)接口,这个接口和枚举很像,但是还提供了删除元素的能力
如果我们要维护一个老项目,这个项目中使用的都是枚举,但是现在我们希望新代码中对集合的操作全部采用迭代器来进行,为了让旧代码中的枚举变得像新代码中的迭代器,我们就需要为枚举制作一个适配器
枚举中的两个方法,迭代器中都有,但是迭代器有删除元素的方法,枚举没有,这时可以选择抛出异常并在文档中说明即可:
/**
* @author 雫
* @date 2021/3/5 - 11:24
* @function
*/
public class EnumeratorIterator implements Iterator {
private Enumeration enumeration;
public EnumeratorIterator(Enumeration enumeration) {
this.enumeration = enumeration;
}
@Override
public boolean hasNext() {
return enumeration.hasMoreElements();
}
@Override
public Object next() {
return enumeration.nextElement();
}
@Override
public void remove() {
throw new UnsupportedOperationException();
}
}
1.5 适配器模式小结
1,当需要使用一个现有的类而其接口不符合需要时,使用适配器
2,适配器改变接口以符合客户的期望
3,实现一个适配器根据目标接口的大小和复杂度而定
2 外观模式
2.1 模拟家庭影院
我们现在要自己搭建一个家庭影院,整个系统需要一些设备:
DVD播放机,投影机,自动屏幕,音响,爆米花机,空调…
我们来粗糙的模拟一下这个系统:
设备:
/**
* @author 雫
* @date 2021/3/5 - 12:18
* @function 音响
*/
public class Amp {
public Amp() {}
public void on() {
System.out.println("打开音响");
}
public void setDvd(Dvd dvd) {
System.out.println("已设置声源为" + dvd);
}
public void setSurroundSound() {
System.out.println("设置为环绕声");
}
public void setVolume(int volume) {
System.out.println("已设置音量为" + volume);
}
public void off() {
System.out.println("关闭音响");
}
}
/**
* @author 雫
* @date 2021/3/5 - 12:16
* @function DVD
*/
public class Dvd {
private String name;
public Dvd(String name) {
this.name = name;
}
public void on() {
System.out.println("打开DVD");
}
public void play(Movie movie) {
System.out.println("播放" + movie);
}
public void off() {
System.out.println("关闭DVD");
}
@Override
public String toString() {
return name;
}
}
...
现在有了这些设备,我们可以看一场电影了,只不过在看电影前和看电影后需要做一些事:
/**
* @author 雫
* @date 2021/3/5 - 11:58
* @function 看一场电影
*/
public class Test {
public static void main(String[] args) {
Amp amp = new Amp();
Dvd dvd = new Dvd("DVD1");
Light light = new Light();
Movie movie = new Movie("xxx");
Popper popper = new Popper();
Projector projector = new Projector();
Screen screen = new Screen();
/*准备看电影*/
popper.on();
popper.pop();
light.on();
light.changeBright(10);
screen.on();
projector.on();
projector.setInput(dvd);
projector.wideScreenMode();
amp.on();
amp.setDvd(dvd);
amp.setSurroundSound();
amp.setVolume(20);
dvd.on();
dvd.play(movie);
/*正在看电影*/
/*电影结束*/
popper.off();
screen.off();
amp.off();
projector.off();
dvd.off();
light.off();
}
}
好了,现在我们的用户想看一场电影,除了有全套的设备外,还需要走来走去前后执行20个操作,我们需要一个类似遥控器的工具,能一键完成看电影前的准备和看电影后的收尾操作
遥控器,执行一组操作,这自然会想到命令模式,采用宏命令来执行一组操作,但是这里有更简单更适合的模式,我们把系统整合到一起
2.2 整合家庭影院系统
我们把所有的设备实体放到另一个类(外观)中,通过组合来完成“一个操作调用一组操作”
整合系统后的HomeTheater:
/**
* @author 雫
* @date 2021/3/5 - 12:43
* @function 家庭影院/外观
*/
public class HomeTheater {
private Amp amp;
private Dvd dvd;
private Light light;
private Movie movie;
private Popper popper;
private Projector projector;
private Screen screen;
public HomeTheater(Amp amp, Dvd dvd, Light light, Movie movie,
Popper popper, Projector projector, Screen screen) {
this.amp = amp;
this.dvd = dvd;
this.light = light;
this.movie = movie;
this.popper = popper;
this.projector = projector;
this.screen = screen;
}
public void startWatch() {
this.popper.on();
this.popper.pop();
this.light.on();
this.light.changeBright(10);
this.screen.on();
this.projector.on();
this.projector.setInput(this.dvd);
this.projector.wideScreenMode();
this.amp.on();
this.amp.setDvd(this.dvd);
this.amp.setSurroundSound();
this.amp.setVolume(20);
this.dvd.on();
this.dvd.play(movie);
}
public void overWatch() {
this.popper.off();
this.screen.off();
this.amp.off();
this.projector.off();
this.dvd.off();
this.light.off();
}
}
简化了用户的操作:
/**
* @author 雫
* @date 2021/3/5 - 11:58
* @function 看一场电影
*/
public class Test {
public static void main(String[] args) {
Amp amp = new Amp();
Dvd dvd = new Dvd("DVD1");
Light light = new Light();
Movie movie = new Movie("xxx");
Popper popper = new Popper();
Projector projector = new Projector();
Screen screen = new Screen();
/*准备看电影*/
HomeTheater homeTheater = new HomeTheater(amp, dvd,
light, movie, popper, projector, screen);
homeTheater.startWatch();
/*电影结束*/
homeTheater.overWatch();
}
}
2.3 外观模式
外观模式:
提供了一个统一的接口,用来访问子系统中的一群接口
外观定义了一个高层接口,让子系统更易使用
2.2中的代码就是将整个影院子系统包装成了一个外观,使得客户从子系统中解耦出来,即客户不需要一个个设备进行操作,只需要对
外观(家庭影院遥控器) 来进行操作
如果客户想要使用子系统中的功能,如增大音响音量,也可以从外观中取得,只需要外观提供额外的方法即可:
public void addVolume(int volume) {
this.amp.setVolume(volume);
}
外观模式并不同于命令模式,命令模式完成了调用者和接收者的完全解耦,而外观模式只是将所有的对象依赖抛给了外观,一旦子系统组件需要更改替换,外观也需要改变,外观并没有实现完全的解耦
2.4 “最少”知识原则
我们根据外观模式,引入新的设计原则:
设计原则:
不要让太多的类耦合在一起
不要让太多的类耦合在一起,免得修改系统中的一部分,会影响到其它部分,如果许多类之间相互依赖,那么这个系统就会变成一个易碎的系统,需要花更多时间维护扩展
我们在2.2中创建的HomeTheater虽然将用户解耦了出来,但它本身却陷入了大量的对象依赖,为了减少这些对象依赖,应该针对抽象编程,而不是针对具体类编程,这里采用命令模式实现这个家庭影院可能会更好
比如我们要从气象站中取得温度,而温度是由气象站内的温度计对象获取的,不采用最少知识原则时:
Station station = new station();
public float getTemp() {
Thermometer thermoeter = station.getThermometer();
return thermometer.getTemperature();
}
采用最少知识原则,尽量减少依赖对象:
//在Station类中新增一个方法
public void getTemoerature() {
return this.thermoeter.getTemperature();
}
Station station = new station();
public float getTemp() {
return station.getTemperature();
}
最少知识原则可以结合依赖倒置原则,降低系统间的耦合,让每个类更“干净”,更健壮
但是采用这个原则会产生更多的“外观类”,这可能会倒置复杂度和开发时间增加,并降低运行时的性能
2.5 适配器模式&外观模式小结
1,当需要一个接口实现多种功能时,采用适配器
2,当需要降低某个类的对象依赖时,采用外观
3,适配器将一个对象包装起来以改变其接口,外观将一群对象包装起来简化接口(接口是抽象的,不一定是interface)