- 所有的鸭子都具有两种行为
**/
//1.呱呱叫
public void quack();
//2.飞行行为
public void fly();
}
绿头鸭是鸭子的子类
/**
- 绿头鸭
*/
public class MallardDuck implements Duck {
@Override
public void quack() {
System.out.println(“呱呱叫”);
}
@Override
public void fly() {
System.out.println(“我会飞”);
}
}
接下来我们引入火鸡类
public interface Turkey {
//只会咯咯叫
void gobble();
//会飞
void fly();
}
public class WildTurkey implements Turkey{
@Override
public void gobble() {
System.out.println(“咯咯叫”);
}
@Override
public void fly() {
System.out.println(“我会飞一小段距离”);
}
}
现在,假设你缺鸭子对象,想用一些火鸡对象来冒充。显而易见,因为火鸡的接口不同,所以我们不能公然拿来用。
为了使火鸡接口能够适配我们的鸭子模型,所以我们需要一个火鸡接口的适配器
/**
- 首先,我们需要实现想转换成的类型接口,也就是你的客户所期望看到的接口。
*/
public class TurkeyAdapter implements Duck {
Turkey turkey;
//接着,需要取得适配的对象引用,这里我们利用构造器取得这个引用。
public TurkeyAdapter(Turkey turkey) {
this.turkey = turkey;
}
@Override
public void quack() {
turkey.gobble();
}
/**
-
显然两个接口都具备了fly()方法,火鸡的飞行距离很短,
-
不像鸭子可以长途飞行。主要让鸭子的飞行和火鸡的飞行能够对应.必须连续五次调用火鸡的fly()来完成。
*/
@Override
public void fly() {
for (int i = 0; i < 5; i++) {
turkey.fly();
}
}
}
测试:
public class DuckTestDriver {
public static void main(String[] args) {
/先创建一只鸭子和一只火鸡/
MallardDuck duck = new MallardDuck();
WildTurkey turkey = new WildTurkey();
/将火鸡包装进一个火鸡适配器中,使他看起来像一只鸭子/
Duck turkeyAdapter = new TurkeyAdapter(turkey);
/测试这只火鸡,让他咯咯叫/
System.out.print(“火鸡叫声:”);
turkey.gobble();
System.out.print(“鸭子叫声:”);
duck.quack();
System.out.print(“使用适配器的鸭子叫声:”);
turkeyAdapter.quack();
System.out.println(“-----------使用适配器的鸭子飞行行为:-------------”);
turkeyAdapter.fly();
}
}
测试结果如下:
现在我们已经知道什么是适配器了,让我们后退一步,再次看看各部分之间的关系。
客户使用适配器的过程如下:
-
1.客户通过目标接口调用适配器的方法对适配器发出请求。
-
2.适配器使用被适配者接口把请求转换成被适配者的一个或多个调用接口。
-
3.客户接收到调用的结果,但并未察觉这一切是适配器在起转换作用。
这个模式可以通过创建适配器进行接口转换,让不兼容的接口变成兼容。这可以让客户从实现的接口解耦。如果在一段时间之后,我们想要改变接口,适配器可以将改变的部分封装起来,客户就不必为了应对不同的接口而每次跟着修改。
实际上有两种适配器:对象适配器,类适配器;
在前面的叙述中,我们已经描述了对象适配器。
而类适配器:
唯一的差别就在于类适配器继承了Target和Adaptee。而对象适配器利用组合的方式将请求传送给被适配者。
枚举器:
迭代器:
接下来我们尝试将枚举器适配到迭代器上
设计适配器
这个类应该是这样的:我们需要一个适配器,实现了目标接口,而此目标接口是由被适配者所组合的。hasNext()和next()方法很容易实现,直接把它们从目标对应到被适配者就可以了。但是对于remove()方法,我们又该怎么办?目前,类图是这样的:
处理remove()方法
我们知道枚举不支持删除,因为枚举是一个“只读”接口。适配器无法实现一个有实际功能的remove()方法,最多只能抛出一个运行时异常。幸运地,迭代器接口的设计者事先料到了这样的需要,所以将remove()方法定义成会抛出UnsupportedOpeartionException。
在这个例子中,我们看到了适配器并不完美,客户必须小心潜在的异常,但只要客户够小心,而且适配器的文档能做出说明,这也算是一个合理的解决方案。
接下来我们开始编写EnumeratorIterator
/**
-
因为我们将枚举追配成迭代器,适配器需要实现迭代器接口
-
适配器必须看起来就像是一个迭代器。
*/
public class EnumeratorIterator implements Iterator {
Enumeration enumeration;
/**
-
我们利用组合的方式,将枚举组合进入适配器中,所以用一个实例变量记录枚举。
-
@param enumeration
*/
public EnumeratorIterator(Enumeration enumeration) {
this.enumeration = enumeration;
}
/**
-
选代器的hasNext()方法其实是委托给枚举的hasMoreElements()方法。
-
@return
*/
@Override
public boolean hasNext() {
return enumeration.hasMoreElements();
}
/**
-
而选代器的next()方法其实是委托给枚举的nextElement()方法
-
@return
*/
@Override
public Object next() {
return enumeration.nextElement();
}
/**
-
很不幸,我们不能支持选代器的remove()方法,所以必须放弃。
-
在这里、我们的做法是抛出一个异常
*/
@Override
public void remove() {
throw new UnsupportedOperationException();
}
}
接下来我们做个测试:
public class EnumerationIteratorTestDrive {
public static void main(String[] args) {
Integer[] list = new Integer[]{1, 2, 3, 4, 5};
Vector v = new Vector(Arrays.asList(list));
Iterator<?> iterator = new EnumeratorIterator(v.elements());
while (iterator.hasNext()) {
System.out.print(iterator.next());
System.out.print(" ");
}
}
}
学到此处,你已经知道适配器模式是如何将一个类的接口转换成另一个符合客户期望的接口的。你也知道在Java中要做到这一点,必须将一个不兼容接口的对象包装起来,变成兼容的对象。
我们现在要看一个改变接口的新模式,但是它改变接口的原因是为了简化接口。这个模式被巧妙地命名为外观模式(Facade-Pattern),之所以这么称呼,是因为它将一个或数个类的复杂的一切都隐藏在背后,只显露出一个干净美好的外观。
接下来我们通过一个简单的例子让展示外观模式的强大之处
有一套系统,内含DVD播放器、投影机、自动屏幕、环绕立体声,甚至还有爆米花机。
接下来就是开始观看电影,但是在观看之前,你需要做以下事情:
但是你将会面临以下麻烦:
-
看完电影后,你还要把一切都关掉,怎么办?难道要反向地把这一切动作再进行一次?
-
如果要听CD或者广播,难道也会这么麻烦?
-
如果你决定要升级你的系统,可能还必须重新学习一套稍微不同的操作过程。
怎么办?使用你的家庭影院竟变得如此复杂!让我们看看外观模式如何解决这团混乱,好让你能轻易地享受电影……
你需要的正是一个外观:有了外观模式,通过实现一个提供更合理的接口的外观类,你可以将一个复杂的子系统变得容易使用。如果你需要复杂子系统的强大威力,别担心,还是可以使用原来的复杂接口的;但如果你需要的是一个方便使用的接口,那就使用外观。
现在,你的客户代码可以调用此家庭影院外观所提供的方法,而不必再调用这个子系统的方法。所以,想要看电影,我们只要调用一个方法(也就是watchMovie())就可以了。灯光、CVD播放器、投影机,屏幕幕、爆米花,一口气全部搞定。
外观只是提供你更直接的操作,未将原来的子系统阻隔起来。如果你需要子系统类的更高层功能,还是可以使用原来的子系统。
外观不只是简化了接口,也将客户从组件的子系统中解耦。
外观和适配器可以包装许多类,但是外观的意图是简化接口,而适配器的意图是将接口转换成不同接口。
接下来我们开始构造家庭影院的外观:
public class HomeTheaterFacade {
Amplifier amp;//扩音器
Tuner tuner;//广播
StreamingPlayer player;//流媒体播放器
CdPlayer cd;//cd
Projector projector;//投影仪
TheaterLights lights;//剧院灯光
Screen screen;//屏幕
PopcornPopper popper;//爆米花
public HomeTheaterFacade(Amplifier amp,
Tuner tuner,
StreamingPlayer player,
Projector projector,
Screen screen,
TheaterLights lights,
PopcornPopper popper) {
this.amp = amp;
this.tuner = tuner;
this.player = player;
this.projector = projector;
this.screen = screen;
this.lights = lights;
this.popper = popper;
}
/**
-
看电影
-
@param movie
*/
public void watchMovie(String movie) {
System.out.println(“Get ready to watch a movie…”);
popper.on();
popper.pop();
lights.dim(10);
screen.down();
projector.on();
projector.wideScreenMode();
amp.on();
amp.setStreamingPlayer(player);
amp.setSurroundSound();
amp.setVolume(5);
player.on();
player.play(movie);
}
/**
- 结束电影
*/
public void endMovie() {
System.out.println(“Shutting movie theater down…”);
其实前端开发的知识点就那么多,面试问来问去还是那么点东西。所以面试没有其他的诀窍,只看你对这些知识点准备的充分程度。so,出去面试时先看看自己复习到了哪个阶段就好。
这里再分享一个复习的路线:(以下体系的复习资料是我从各路大佬收集整理好的)
《前端开发四大模块核心知识笔记》
最后,说个题外话,我在一线互联网企业工作十余年里,指导过不少同行后辈。帮助很多人得到了学习和成长。
我意识到有很多经验和知识值得分享给大家,也可以通过我们的能力和经验解答大家在IT学习中的很多困惑,所以在工作繁忙的情况下还是坚持各种整理和分享。