接口隔离模式
使用场景
在组件构建过程中, 某些接口之间直接的依赖常常会带来很多问题、甚至根本无法实现。采用添加一层间接(稳定)接口,来隔离本来互相紧密关联的接口是一种常见的解决方案。
典型模式
●Fagade(外观模式)
●Proxy(代理模式)
●Adapter(适配器)
●Mediator
Facade 外观模式
外观模式(Facade Pattern)隐藏系统的复杂性,并向客户端提供了一个客户端可以访问系统的接口。这种类型的设计模式属于结构型模式,它向现有的系统添加一个接口,来隐藏系统的复杂性。
这种模式涉及到一个单一的类,该类提供了客户端请求的简化方法和对现有系统类方法的委托调用。
意图:为子系统中的一组接口提供一个一致的界面,外观模式定义了一个高层接口,这个接口使得这一子系统更加容易使用。
主要解决:降低访问复杂系统的内部子系统时的复杂度,简化客户端与之的接口。
何时使用: 1、客户端不需要知道系统内部的复杂联系,整个系统只需提供一个"接待员"即可。 2、定义系统的入口。
如何解决:客户端不与系统耦合,外观类与系统耦合。
关键代码:在客户端和复杂系统之间再加一层,这一层将调用顺序、依赖关系等处理好。
应用实例: 1、去医院看病,可能要去挂号、门诊、划价、取药,让患者或患者家属觉得很复杂,如果有提供接待人员,只让接待人员来处理,就很方便。 2、JAVA 的三层开发模式。
优点: 1、减少系统相互依赖。 2、提高灵活性。 3、提高了安全性。
缺点:不符合开闭原则,如果要改东西很麻烦,继承重写都不合适。
使用场景: 1、为复杂的模块或子系统提供外界访问的模块。 2、子系统相对独立。 3、预防低水平人员带来的风险。
注意事项:在层次化结构中,可以使用外观模式定义系统中每一层的入口。
系统间耦合的复杂度
A方案灰色框框里面是一个子系统,A方案外部和子系统内部类有各种交错依赖情况。
B方案用了一层Facade隔离子系统和外界的访问关系,外界只能访问Facade接口,不能直接访问子系统的类。
使用场景
上述 A方案的问题在于组件的客户和组件中各种复杂的子系统有了过多的耦合,随着外部客户程序和各子系统的演化,这种过多的耦合面临很多变化的挑战。
"间接"即"解耦"
人---硬件
人---软件---硬件 软件就是 人 和 硬件 的隔离层
人---应用软件---操作系统--硬件 操作系统就是 应用软件 和 硬件 的隔离层
人---应用软件---虚拟机--操作系统--硬件 虚拟机就是 应用软件 和 操作系统 的隔离层
模式定义
为子系统中的一-组接口提供一个一致(稳定)的界面,Facade模式定义了一个高层接口,这个接口使得这一子系统更加容易使用(复用)。
结构
要点总结
从客户程序的角度来看,Facade模式简化 了整个组件系统的接口,对于组件内部与外部客户程序来说,达到了一种“解耦”的效果内部子系统的任何变化不会影响到Facade接口的变化。
Facade设计模式更注重从架构的层次去看整个系统,而不是单个类的层次。Fagade很多时候更是一种架构设计模式。
Facade设计模式并非一一个集装箱,可以任意地放进任何多个对象。Facade模式中组件的内部应该是‘相互耦合关系比较大的一系列组件”,而不是一个简单的功能集合。
Proxy 代理模式
在代理模式(Proxy Pattern)中,一个类代表另一个类的功能。这种类型的设计模式属于结构型模式。
在代理模式中,我们创建具有现有对象的对象,以便向外界提供功能接口。
意图:为其他对象提供一种代理以控制对这个对象的访问。
主要解决:在直接访问对象时带来的问题,比如说:要访问的对象在远程的机器上。在面向对象系统中,有些对象由于某些原因(比如对象创建开销很大,或者某些操作需要安全控制,或者需要进程外的访问),直接访问会给使用者或者系统结构带来很多麻烦,我们可以在访问此对象时加上一个对此对象的访问层。
何时使用:想在访问一个类时做一些控制。
如何解决:增加中间层。
关键代码:实现与被代理类组合。
应用实例: 1、Windows 里面的快捷方式。 2、猪八戒去找高翠兰结果是孙悟空变的,可以这样理解:把高翠兰的外貌抽象出来,高翠兰本人和孙悟空都实现了这个接口,猪八戒访问高翠兰的时候看不出来这个是孙悟空,所以说孙悟空是高翠兰代理类。 3、买火车票不一定在火车站买,也可以去代售点。 4、一张支票或银行存单是账户中资金的代理。支票在市场交易中用来代替现金,并提供对签发人账号上资金的控制。 5、spring aop。
优点: 1、职责清晰。 2、高扩展性。 3、智能化。
缺点: 1、由于在客户端和真实主题之间增加了代理对象,因此有些类型的代理模式可能会造成请求的处理速度变慢。 2、实现代理模式需要额外的工作,有些代理模式的实现非常复杂。
使用场景:按职责来划分,通常有以下使用场景: 1、远程代理。 2、虚拟代理。 3、Copy-on-Write 代理。 4、保护(Protect or Access)代理。 5、Cache代理。 6、防火墙(Firewall)代理。 7、同步化(Synchronization)代理。 8、智能引用(Smart Reference)代理。
注意事项: 1、和适配器模式的区别:适配器模式主要改变所考虑对象的接口,而代理模式不能改变所代理类的接口。 2、和装饰器模式的区别:装饰器模式为了增强功能,而代理模式是为了加以控制。
使用场景
在面向对象系统中,有些对象由于某种原因(比如对象创建的开销很大,或者某些操作需要安全控制,或者需要进程外的访问等),直接访问会给使用者、或者系统结构带来很多麻烦。
模式定义
为其他对象提供一种代理以控制(隔离,使用接口)对这个对象的访问。
类图结构
代码举例
不使用代理设计模式,常规做法。
如果/ 上方代码存放在A进程当中,下发代码存放在B进程
subject=new RealSubject(); 代码就无法使用
//进程A
public interface ISubject{
public void process();
}
public class RealSubject implements ISubject{
public void process(){
//....
}
}
//
//进程B
public class ClientApp{
ISubject subject;
public ClientApp(){
subject=new RealSubject();
}
public void DoTask(){
//...
subject.process();
//....
}
}
使用代理设计模式 跨进程
//进程A
public interface ISubject{
public void process();
}
public class RealSubject implements ISubject{
public void process(){
//...
}
}
//
//进程B
public interface ISubject{
public void process();
}
//Proxy的设计
public class SubjectProxy implements ISubject {
//RealSubject realSubject;
public void process(){
//对RealSubject的一种间接访问
//realSubject.process();
}
}
public class ClientApp{
ISubject subject;
public ClientApp(){
subject=new SubjectProxy();
}
public void DoTask(){
//...
subject.process();
//....
}
}
类图中的Subject 对应代码中的 ISubject,Request() 对应代码中的 process()。RealSubject 对应代码中的 RealSubject。Proxy 对应代码中的 SubjectProxy。
要点总结
“增加一层间接层’是软件系统中对许多复杂问题的一种常见解决方法。在面向对象系统中,直接使用某些对象会带来很多问题,作为间接层的proxy对象便是解决这一问题的常用手段。
具体proxy设计模式的实现方法、实现粒度都相差很大,有些可能对单个对象做细粒度的控制,如copy-on-write技术, 有些可能对组件模块提供抽象代理层,在架构层次对对象做proxy。
Proxy并不一定要 求保持接口完整的一致性,只要能够实现间接控制,有时候损及一-些透明性是可以接受的。
Adapter 适配器
适配器模式(Adapter Pattern)是作为两个不兼容的接口之间的桥梁。这种类型的设计模式属于结构型模式,它结合了两个独立接口的功能。
这种模式涉及到一个单一的类,该类负责加入独立的或不兼容的接口功能。举个真实的例子,读卡器是作为内存卡和笔记本之间的适配器。您将内存卡插入读卡器,再将读卡器插入笔记本,这样就可以通过笔记本来读取内存卡。
意图:将一个类的接口转换成客户希望的另外一个接口。适配器模式使得原本由于接口不兼容而不能一起工作的那些类可以一起工作。
主要解决:主要解决在软件系统中,常常要将一些"现存的对象"放到新的环境中,而新环境要求的接口是现对象不能满足的。
何时使用: 1、系统需要使用现有的类,而此类的接口不符合系统的需要。 2、想要建立一个可以重复使用的类,用于与一些彼此之间没有太大关联的一些类,包括一些可能在将来引进的类一起工作,这些源类不一定有一致的接口。 3、通过接口转换,将一个类插入另一个类系中。(比如老虎和飞禽,现在多了一个飞虎,在不增加实体的需求下,增加一个适配器,在里面包容一个虎对象,实现飞的接口。)
如何解决:继承或依赖(推荐)。
关键代码:适配器继承或依赖已有的对象,实现想要的目标接口。
应用实例: 1、美国电器 110V,中国 220V,就要有一个适配器将 110V 转化为 220V。 2、JAVA JDK 1.1 提供了 Enumeration 接口,而在 1.2 中提供了 Iterator 接口,想要使用 1.2 的 JDK,则要将以前系统的 Enumeration 接口转化为 Iterator 接口,这时就需要适配器模式。 3、在 LINUX 上运行 WINDOWS 程序。 4、JAVA 中的 jdbc。
优点: 1、可以让任何两个没有关联的类一起运行。 2、提高了类的复用。 3、增加了类的透明度。 4、灵活性好。
缺点: 1、过多地使用适配器,会让系统非常零乱,不易整体进行把握。比如,明明看到调用的是 A 接口,其实内部被适配成了 B 接口的实现,一个系统如果太多出现这种情况,无异于一场灾难。因此如果不是很有必要,可以不使用适配器,而是直接对系统进行重构。 2.由于 JAVA 至多继承一个类,所以至多只能适配一个适配者类,而且目标类必须是抽象类。
使用场景:有动机地修改一个正常运行的系统的接口,这时应该考虑使用适配器模式。
注意事项:适配器不是在详细设计时添加的,而是解决正在服役的项目的问题。
使用场景
在软件系统中,由于应用环境的变化,常常需要将“一些现存的对象”放在新的环境中应用,但是新环境要求的接口是这些现存对象所不满足的。
模式定义
将一个类的接口转换成客户希望的另一个接口。Adapter模式使得原本由于接口不兼容而不能一起工作的那些类可以一起工作。
类图结构
//目标接口(新接口)
public interface ITarget{
public void process();
}
//目标环境(新环境)
public class TargetLibrary{
public void invoke(ITarget target){
target.process();
}
}
//现存接口(老接口)
public interface IAdaptee{
public void foo(int data);
public int bar();
}
//现存类型
public class OldClass implements IAdaptee{
//....
}
//对象适配器
public class Adapter implements ITarget{ //继承
IAdaptee adaptee;//组合
public Adapter(IAdaptee adaptee){
this.adaptee=adaptee;
}
public void process(){
int data=adaptee.bar();
adaptee.foo(data);
}
}
public class ClientApp {
public static void main(String args[]) {
IAdaptee adaptee = new OldClass();
ITarget target = new Adapter(adaptee);
TargetLibrary library=new TargetLibrary();
library.invoke(target);
}
}
类图结构中的 Target 对应代码中的 ITarget。Client 对应代码中的 TargetLibrary。Adaptee 对应代码中的 IAdaptee。
Android使用Adapter设计模式的体现
/*
* Copyright (C) 2007 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.example.android.apis.view;
import com.example.android.apis.Shakespeare;
import android.app.ListActivity;
import android.content.Context;
import android.os.Bundle;
import android.view.View;
import android.view.ViewGroup;
import android.widget.BaseAdapter;
import android.widget.LinearLayout;
import android.widget.TextView;
/**
* A list view example where the data comes from a custom ListAdapter
*/
public class List4 extends ListActivity {
@Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
// Use our own list adapter
setListAdapter(new SpeechListAdapter(this)); //相当于目标接口
}
/**
* A sample ListAdapter that presents content from arrays of speeches and
* text.
*
*/
//设计适配器
private class SpeechListAdapter extends BaseAdapter {
public SpeechListAdapter(Context context) {
mContext = context;
}
/**
* The number of items in the list is determined by the number of speeches
* in our array.
*
* @see android.widget.ListAdapter#getCount()
*/
public int getCount() {
return Shakespeare.TITLES.length;
}
/**
* Since the data comes from an array, just returning the index is
* sufficent to get at the data. If we were using a more complex data
* structure, we would return whatever object represents one row in the
* list.
*
* @see android.widget.ListAdapter#getItem(int)
*/
public Object getItem(int position) {
return position;
}
/**
* Use the array index as a unique id.
*
* @see android.widget.ListAdapter#getItemId(int)
*/
public long getItemId(int position) {
return position;
}
/**
* Make a SpeechView to hold each row.
*
* @see android.widget.ListAdapter#getView(int, android.view.View,
* android.view.ViewGroup)
*/
public View getView(int position, View convertView, ViewGroup parent) {
SpeechView sv;
if (convertView == null) {
sv = new SpeechView(mContext, Shakespeare.TITLES[position],
Shakespeare.DIALOGUE[position]);
} else {
sv = (SpeechView) convertView;
sv.setTitle(Shakespeare.TITLES[position]);
sv.setDialogue(Shakespeare.DIALOGUE[position]);
}
return sv;
}
/**
* Remember our context so we can use it when constructing views.
*/
private Context mContext;
}
/**
* We will use a SpeechView to display each speech. It's just a LinearLayout
* with two text fields.
*
*/
//SpeechView 想要适配到ListView里边 需要一个适配器
private class SpeechView extends LinearLayout {
public SpeechView(Context context, String title, String words) {
super(context);
this.setOrientation(VERTICAL);
// Here we build the child views in code. They could also have
// been specified in an XML file.
mTitle = new TextView(context);
mTitle.setText(title);
addView(mTitle, new LinearLayout.LayoutParams(
LayoutParams.MATCH_PARENT, LayoutParams.WRAP_CONTENT));
mDialogue = new TextView(context);
mDialogue.setText(words);
addView(mDialogue, new LinearLayout.LayoutParams(
LayoutParams.MATCH_PARENT, LayoutParams.WRAP_CONTENT));
}
/**
* Convenience method to set the title of a SpeechView
*/
public void setTitle(String title) {
mTitle.setText(title);
}
/**
* Convenience method to set the dialogue of a SpeechView
*/
public void setDialogue(String words) {
mDialogue.setText(words);
}
private TextView mTitle;
private TextView mDialogue;
}
}
要点总结
Adapter模式主要 应用于“希望复用一些现存的类,但是接口又与复用环境要求不一致的情况”,在遗留代码复用、类库迁移等方面非常有用。
GoF 23定义了两种Adapter模式的实现结构:对象适配器和类适配器。但类适配器采用“多继承”的实现方式,一般不推荐使用。对象适配器采用“对象组合”的方式,更符合松耦合精神。
Adapter模式可以实现的非常灵活,不必拘泥于Gof23中定义的两种结构。例如,完全可以将Adapter模式中的'现存对象”作为新的接口方法参数,来达到适配的目的。