前言:
由于项目变更的频繁性,作为一名程序员,我们需要掌握设计模式的必要性,就不言而喻~~,下面就是一些我自己学习的设计模式总结。
接下来,主要是针对几个比较常用模式进行讲解,主要是以下几种:
- 观察者模式
- 适配器模式
- 代理模式
- 工厂模式
- 单例模式
- 命令模式
一,观察者模式(Observer Pattern)
释义:观察者模式定义了一种一对多的依赖关系,让多个观察者对象同时监听某一个主题对象,这个主题对象在状态上发生变化时,会通知所有观察者对象,使他们能够自动更新自己。
故事理解:观察者想知道公司所有MM的情况,只要加入公司的MM情报邮件组就行了,tom负责搜集情报,当发现新情报时,不用一个一个通知我们,直接发布给邮件组,我们作为订阅者(观察者)就可以及时收到情报啦。
常见实例:1.BaseAdapter.registerDataSetObserver和BaseAdapter.unregisterDataSetObserver两方法来向BaseAdater注册、注销一个DataSetObserver ; 2.使用ContentObserver去监听数据库变化。
适用场景:1.当对一个对象的改变需要同时改变其他对象,而不知道具体有多少对象有待改变;2.当一个对象必须通知其它对象,而它又不能假定其它对象是谁.
观察者模式主要有观察者和被观察者2个对象,在该模式中,Observable表示被观察者,这个对象是一个抽象类,只能被继承。 Observer表示观察者,他是一个接口,所以观察者可以有多个,实现了该接口的类都是属于观察者。
这是网上一个生动细致的demo:
被观察者:
public class MyPerson extends Observable {
private int age;
private String name;
private String sax;
public int getAge() {
return age;
}
public void setAge(int age) {
this.age = age;
setChanged();
notifyObservers();
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
setChanged();
notifyObservers();
}
public String getSax() {
return sax;
}
public void setSax(String sax) {
this.sax = sax;
}
@Override
public String toString() {
return "MyPerson [age=" + age + ", name=" + name + ", sax=" + sax + "]";
}
}
MyPerson是被观察者,类中调用了setChange()以及notifyObservers()两个方法,前者是告知数据改变,后者是发送信号通知观察者。
观察者:
public class MyObserver implements Observer {
private int i;
private MyPerson myPerson;//观察的对象
public MyObserver(int i){
System.out.println("我是观察者---->" + i);
this.i = i;
}
public int getI() {
return i;
}
public void setI(int i) {
this.i = i;
}
public MyPerson getMyPerson() {
return myPerson;
}
public void setMyPerson(MyPerson myPerson) {
this.myPerson = myPerson;
}
@Override
public void update(Observable observable, Object data) {
System.out.println("观察者---->"+ i +"得到更新!");
this.myPerson = (MyPerson)observable;
System.out.println(((MyPerson)observable).toString());
}
}
观察者需要实现Observer接口,其中只有一个update方法,当观察者收到 (被观察者)的通知信号,就会执行该动作。
二,适配器模式(Adapter Pattern)
释义:把一个类的接口变换成客户端所期待的另一种接口,从而使原本因接口原因不匹配而无法一起工作的两个类能够一起工作。适配类可以根据参数返还一个合适的实例给客户端。
故事理解:在朋友聚会上碰到了一个美女Sarah,从香港来的,可我不会说粤语,她不会说普通话,只好求助于我的朋友kent了,他作为我和Sarah之间的Adapter,让我和Sarah可以相互交谈了(也不知道他会不会耍我)。
常见实例:ListView用于显示列表数据,但是作为列表数据集合有很多形式,有Array,有Cursor,我们需要对应的适配器作为桥梁,处理相应的数据(并能形成ListView所需要的视图)。
适用场景:1.业务的接口与工作的类不兼容,(比如:类中缺少实现接口的某些方法)但又需要两者一起工作;2. 在现有接口和类的基础上为新的业务需求提供接口。
适配器模式分为类适配器模式和对象适配器模式。关于类适配模式,因为java的单继承,所以在已继承一个类时,另外的只能是接口,需要手动实现相应的方法,这样在客户端就可以创建任一种符合需求的子类,来实现具体功能。而另外一种对象适配器,它不是使用继承再实现的方式,而是使用直接关联,或者称为委托的方式,具体可见该博客详细介绍适配器模式(Adapter):类适配器、对象适配器。
接下来就以ListView与ArrayAdapter来讲解下
ListAdapter:
public interface ListAdapter {
public int getCount();
Object getItem(int position);
long getItemId(int position);
View getView(int position, View convertView, ViewGroup parent);
boolean isEmpty();
}
ListView作为一个客户端,所需要的目标接口就是ListAdapter,包含getCount(),getItem(),getView()等几个方法,为了兼容List< T >数据类型的数据源,专门定义了ArrayAdapter适配器,说白了,就是针对目标接口对数据源进行兼容修饰。
抽象类BaseAdapter,省略了其他代码,这里只列出两个方法:
public abstract class BaseAdapter implements ListAdapter, SpinnerAdapter {
// ... ...
public View getDropDownView(int position, View convertView, ViewGroup parent) {
return getView(position, convertView, parent);
}
public boolean isEmpty() {
return getCount() == 0;
}
}
ArrayAdapter对List< T >进行封装成ListAdapter的实现,满足ListView的调用:
public class ArrayAdapter<T> extends BaseAdapter implements Filterable {
private List<T> mObjects;
//我只列出这一个构造函数,大家懂这个意思就行
public ArrayAdapter(Context context, int textViewResourceId, T[] objects) {
init(context, textViewResourceId, 0, Arrays.asList(objects));
}
private void init(Context context, int resource, int textViewResourceId, List<T> objects) {
mContext = context;
mInflater = (LayoutInflater)context.getSystemService(Context.LAYOUT_INFLATER_SERVICE);
mResource = mDropDownResource = resource;
mObjects = objects; //引用对象,也是表达了组合优于继承的意思
mFieldId = textViewResourceId;
}
public int getCount() {
return mObjects.size();
}
public T getItem(int position) {
return mObjects.get(position);
}
public View getView(int position, View convertView, ViewGroup parent) {
return createViewFromResource(position, convertView, parent, mResource);
}
// ... ...
}
我们就如此成功的把List< T > 作为数据源以ListView想要的目标接口的样子传给了ListView。这其实也是对象适配器的一种。
三,代理模式(Proxy Pattern)
释义: 通过引入一个新的对象来实现对真实对象的操作或将新的对象作为真实对象的一个替身,这样的实现机制是代理模式(为其他对象提供一种代理以控制对这个对象的访问).
故事理解:校园代理是为他对应的上司作代理,而这个校园代理的工作就是访问校园中的学生,例如对学生进行问卷之类的事。学生就是官方说法中的其他对象,校园代理的上司就通过控制这个校园代理来控制对学生的访问。
常见实例:ActivityManagerProxy类就是一个代理,它是ActivityManagerNative的代理,也就是说ActivityManagerProxy是所说的Proxy类,而ActivityManagerNative就相当于“上司角色“类,它们都有一个共有的接口IActivityManager。ActivityManager,它相当于代理模式的client。在这个类中,可以看到大量的getxxx函数,这些函数,都会调用到ActivityManagerNative类的getDefault()方法,而该方法会获得一个共用的单例的IActivityManager引用,然后通过多态来调用代理中的实现
适用场景:代理模式的应用场合分几种:远程代理,虚拟代理,安全代理等,具体可见为别人做嫁衣—-代理模式 。
请看下面一张结构 图(网上看到的一个细致生动的例子):
Subject类:定义了RealSubject和Proxy的共用接口,这样就可以在任何 使用RealSubject的地方都可以用Proxy。
RealSubject类:定义Proxy所代表的真实实体。
Proxy类:保存一个引用使得代理可以访问实体,并提供一个与Subject的接口相同的接口,这样代理就可以用来替代实体。
接下来,我们来实现该模式:
1.Subject类 :Image.java
/**
* Subject类
*/
public abstract class Image {
public abstract void displayImage();
}
2.RealSubject类:RealImage.java
import com.andyidea.patterns.subject.Image;
/**
* RealSubject类
*/
public class RealImage extends Image {
private String filename;
public RealImage(String filename) {
this.filename = filename;
loadImageFromDisk();
}
private void loadImageFromDisk() {
System.out.println("Loading " + filename);
}
@Override
public void displayImage() {
System.out.println("Displaying " + filename);
}
}
3.proxy类:ProxyImage.java
import com.andyidea.patterns.realsubject.RealImage;
import com.andyidea.patterns.subject.Image;
/**
* Proxy类
*/
public class ProxyImage extends Image {
private String filename;
private Image image;
public ProxyImage(String filename) {
this.filename = filename;
}
@Override
public void displayImage() {
if(image == null){
image = new RealImage(filename);
}
image.displayImage();
}
}
4.客户端测试类:ProxyClient.java
import com.andyidea.patterns.proxy.ProxyImage;
import com.andyidea.patterns.subject.Image;
/**
* 代理模式客户端测试类
*/
public class ProxyClient {
public static void main(String[] args) {
System.out.println("Welcome to my Blog!" +"\n"
+"Proxy Patterns." +"\n"
+"-------------------------------");
Image mImage1 = new ProxyImage("My.Image1");
Image mImage2 = new ProxyImage("My.Image2");
mImage1.displayImage();
mImage2.displayImage();
}
}
运行结果如下:
Welcome to my Blog!
Proxy Patterns
-------------------------------
Loading My.Image1
Displaying My.Image1
Loading My.Image2
Displaying My.Image2
结来说,代理类就是对实体保存一个引用,同时也实现了实体一样的接口方法,这样,就可以替代实体了!!
四,工厂模式(Factory Pattern)
工厂模式分为简单工厂模式,工厂方法模式以及抽象工厂模式
简单工厂模式:一般情况下,提供一个方法,方法的参数是一个标志位,根据标志位来创建不同的对象,这样调用的时候只需要提供一个标志位就可以创建一个实现了接口的类。
工厂方法模式:将简单工厂模式的那个方法分开,不再是在工厂方法中根据标志位创建对象了。而是定义一个工厂接口,然后想创建几个不同类型的对象(即实现了同一接口的不同java类),就创建了几个不同类型的工厂。也就是创建的对象和创建对象的工厂是一一对应的。然后客户端调用的时候直接去实例化一个具体的对象工厂,创建相对应的对象。
抽象工厂模式:其实这个名起的有点不知所云没有表达出这个模式的特点。其实这个模式就是工厂方法模式的稍微扩展一下而已。工厂方法模式里面,一般一个工厂接口只有一个方法,比如createMouse()。然后用实现了这个接口的具体工厂类只能生产鼠标。而抽象工厂模式就是一个工厂接口有多个方法,比如createMouse() , createKeyboard() 。 这样实现了这个工厂接口的具体工厂类就可以既生产鼠标又生产键盘。
常见实例:比如android的bitmap中常用的BitmapFactory类,创建Bitmap对象,通常使用静态工厂方法
这里主要介绍简单工厂与工厂方法的区别:
就以大话模式中小菜跟大鸟举得雷锋故事作为题材吧。
LeiFeng类:
//雷锋
public interface LeiFeng {
void sweep();//扫地
void wash();//洗衣
void buyrice();//做饭
}
Student类:
//学做雷锋的大学生
public class Student implements LeiFeng{
public void buyrice() {
System.out.println("大学生做饭");
}
public void sweep() {
// TODO Auto-generated method stub
System.out.println("大学生扫地");
}
public void wash() {
// TODO Auto-generated method stub
System.out.println("大学生洗衣");
}
}
Valuator类志愿者:
//学做雷锋的志愿者
public class Valuator implements LeiFeng{
public void buyrice() {
System.out.println("志愿者做饭");
}
public void sweep() {
// TODO Auto-generated method stub
System.out.println("志愿者扫地");
}
public void wash() {
// TODO Auto-generated method stub
System.out.println("志愿者洗衣");
}
}
然后简单工厂是这么实现的:
//使用简单工厂
public class SimpleFactory {
public static LeiFeng createLeiFeng(String type){
if("大学生".equals(type)){
return new Student();
}else if("志愿者".equals(type)){
return new Valuator();
}
return null;
}
}
而工厂方法模式中,则多了一个接口去创建不同类型的对象:
Factory类:
//工厂方法模式,工厂接口
public interface Factory {
LeiFeng createLeiFengFactory();
}
StudentFactory学生工厂:
//学生工厂
public class StudentFactory implements Factory{
public LeiFeng createLeiFengFactory() {
return new Student();
}
}
ValuatorFactory志愿者工厂:
//志愿者工厂
public class ValuatorFactory implements Factory{
public LeiFeng createLeiFengFactory() {
return new Valuator();
}
}
当我们实现起来时:
public static void main(String[] args) {
//简单工厂模式
LeiFeng f11=SimpleFactory.createLeiFeng("大学生");
f11.buyrice();
LeiFeng f22=SimpleFactory.createLeiFeng("大学生");
f22.wash();
//使用工厂方法模式
Factory fac=new StudentFactory();
LeiFeng f4=fac.createLeiFengFactory();
f4.buyrice();
LeiFeng f5=fac.createLeiFengFactory();
f5.wash();
}
这里就要说说为什么要使用工厂方法模式,因为简单工厂使用起来明显要方便简约的多。从理论的角度来说,工厂方法模式更符合封闭-开放原则。即对修改封闭对扩展开放。试想后期维护过程中要增加一个种类的对象,也就是增加对接口的一种实现,简单工厂模式就要在switch…case中增加一个case项,无疑是修改了工厂方法。如果是jar包模式的,就要重新发包了。但是工厂方法模式,完全不需要更改工厂接口,只是新增加一个实现的工厂类即可(如果是jar包模式的,就可以不用重新发jar包,让用jar包的人自己去扩展一个实现了工厂接口的具体工厂类即可)。完全符合封闭-扩展原则。
五,单例模式(Single Pattern)
释义:单例模式确保某一个类只有一个实例,而且自行实例化并向整个系统提供这个实例单例模式。单例模式只应在有真正的“单一实例”的需求时才可使用。
故事理解:俺有6个漂亮的老婆,她们的老公都是我,我就是我们家里的老公Sigleton,她们只要说道“老公”,都是指的同一个人,那就是我
常见实例:数据库创建时使用单例模式,Servlet环境下共享同一个资源或者对象
适用场景:对于定义的一个类,在整个应用程序执行期间只有唯一的一个实例对象。如Android中常见的Application对象。
单例模式可分为饿汉式,懒汉式等:
(一)饿汉式:其特点是应用中尚未需要用到此单一实例的时候即先实例化。
public class SingleTon {
// 静态实例变量,直接初始化
private static SingleTon instance = new SingleTon();
// 私有化构造函数
private SingleTon() {
}
// 静态public方法,向整个应用提供单例获取方式
public static SingleTon getInstance() {
return instance;
}
}
(二)懒汉式:其特点是延迟加载,即当需要用到此单一实例的时候,才去初始化此单一实例。
public class SingletonA {
/**
* 单例对象实例
*/
private static SingletonA instance = null;
public static SingletonA getInstance() {
if (instance == null) { //line 12
instance = new SingletonA(); //line 13
}
return instance;
}
}
在这里要说下懒汉式,因为它具有一定的缺陷,我们可以假设这样的场景:两个线程并发调用Singleton.getInstance(),假设线程一先判断完instance是否为null,既代码中的line 12进入到line 13的位置。刚刚判断完毕后,JVM将CPU资源切换给线程二,由于线程一还没执行line 13,所以instance仍然是空的,因此线程二执行了new Signleton()操作。片刻之后,线程一被重新唤醒,它执行的仍然是new Signleton()操作。
所以对它进行了改良:
public class SingletonB {
/**
* 单例对象实例
*/
private static SingletonB instance = null;
public synchronized static SingletonB getInstance() {
if (instance == null) { //line 12
instance = new SingletonB(); //line 13
}
return instance;
}
}
往方法上加了个同步锁,这样就可以保证不会出线程问题了,但是这里有个很大(至少耗时比例上很大)的性能问题。除了第一次调用时是执行了SingletonB的构造函数之外,以后的每一次调用都是直接返回instance对象。返回对象这个操作耗时是很小的,绝大部分的耗时都用在synchronized修饰符的同步准备上,因此从性能上说很不划算。
所以又进行了改进:
public class SingletonC {
/**
* 单例对象实例
*/
private static SingletonKerriganD instance = null;
public static SingletonC getInstance() {
if (instance == null) {
synchronized (SingletonC.class) {
if (instance == null) {
instance = new SingletonC();
}
}
}
return instance;
}
}
目前我用的版本也就是这种。然而,网上有人又对这种单例模式进行了改进,因为还是存在缺陷,具体可以去网上拓展下。再说说饿汉式的写法,这种写法不会出现并发问题,在ClassLoader加载类后实例就会第一时间被创建。但饿汉式的创建方式在一些场景中将无法使用:譬如实例的创建是依赖参数或者配置文件的,在getInstance()之前必须调用某个方法设置参数给它,那样这种单例写法就无法使用了。
六,命令模式(Command Pattern)
释义:把一个请求或者操作封装到一个对象中。命令模式把发出命令的责任和执行命令的责任分割开,委派给不同的对象。命令模式允许请求的一方和发送的一方独立开来,使得请求的一方不必知道接收请求的一方的接口,更不必知道请求是怎么被接收,以及操作是否执行,何时被执行以及是怎么被执行的。
故事理解:俺有一个MM家里管得特别严,没法见面,只好借助于她弟弟在我们俩之间传送信息,她对我有什么指示,就写一张纸条让她弟弟带给我。这不,她弟弟又传送过来一个COMMAND,为了感谢他,我请他吃了碗杂酱面,哪知道他说:“我同时给我姐姐三个男朋友送COMMAND,就数你最小气,才请我吃面。
常见实例:常用的Runnable(在java.lang包下),其实就是用了命令模式
适用场景:1. 命令的发送者和命令执行者有不同的生命周期。命令发送了并不是立即执行。2. 命令需要进行各种管理逻辑。3. 需要支持撤消\重做操作(这种状况的代码大家可以上网搜索下,有很多,这里不进行详细解读)。
其实经典的命令模式包括4个角色:
Command:定义命令的统一接口
ConcreteCommand:Command接口的实现者,用来执行具体的命令,某些情况下可以直接用来充当Receiver。
Receiver:命令的实际执行者
Invoker:命令的请求者,是命令模式中最重要的角色。这个角色用来对各个命令进行控制。
接下来,就以小菜大鸟去烧烤店,给服务员报菜,然后服务员通知厨师为例子。
Command类 :
/*
* 抽象命令
*/
abstract class Command {
protected Barbecuer barbecuer;
public Command(Barbecuer barbecuer) {
this.barbecuer = barbecuer;
}
// 执行命令
public abstract void excuteCommand();
}
(Receiver类)Barbecuer:
/*
* 烤肉串者
*/
class Barbecuer {
// 烤羊肉串
public void bakeMutton() {
System.out.println("烤羊肉串!");
}
// 烤鸡翅
public void bakeChickenWing() {
System.out.println("烤鸡翅!");
}
}
(ConcreteCommand类) BakeMuttonCommand、BakeChickenWingCommand:
/*
* 烤羊肉串命令
*/
class BakeMuttonCommand extends Command {
public BakeMuttonCommand(Barbecuer barbecuer) {
super(barbecuer);
}
@Override
public void excuteCommand() {
barbecuer.bakeMutton();
}
@Override
public String toString() {
return "命令模式,烤羊肉串命令!";
}
}
/*
* 烤鸡翅命令
*/
class BakeChickenWingCommand extends Command {
public BakeChickenWingCommand(Barbecuer barbecuer) {
super(barbecuer);
}
@Override
public void excuteCommand() {
barbecuer.bakeChickenWing();
}
@Override
public String toString() {
return "命令模式,烤鸡翅命令! ";
}
}
(Invoker类)Waiter :
/*
* 服务员
*/
class Waiter {
SimpleDateFormat simpleDateFormat = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
private List<Command> orders = new ArrayList<Command>();
// 设定订单
public void setOrder(Command command) {
orders.add(command);
System.out.println("增加订单:" + command.toString() + " \t时间:" + simpleDateFormat.format(new Date()));
}
// 取消订单
public void cancelOrder(Command command) {
orders.remove(command);
System.out.println("取消订单:" + command.toString() + " \t时间:" + simpleDateFormat.format(new Date()));
}
// 通知全部执行
public void notifyA() {
for (Command command : orders) {
command.excuteCommand();
}
}
}
Client类实现(如下):
public class CommandMode {
public static void main(String[] args) {
// 开店前准备
Barbecuer boy = new Barbecuer();
Command bakeMuttonCommand1 = new BakeMuttonCommand(boy);
Command bakeMuttonCommand2 = new BakeMuttonCommand(boy);
Command bakeChickenWingCommand = new BakeChickenWingCommand(boy);
Waiter girl = new Waiter();
// 开门营业
girl.setOrder(bakeMuttonCommand1);
girl.setOrder(bakeMuttonCommand2);
girl.setOrder(bakeChickenWingCommand);
// 点菜完毕,通知厨房
girl.notifyA();
}
}
总结来说,命令模式是将功能提升到对象来操作,以便对多个功能进行一系列的处理以及封装。
这里要建议下命令模式的使用,当我们不清楚是否需要使用时,一般不用着急去实现它。事实上,在需要的时候通过重构实现这个模式并不困难,只有在真正需要如撤销/恢复操作等功能时,把原来的代码重构为命令模式才有意义。
最后,设计模式的运用,有助于代码的维护与拓展。任何模式的出现,都是为了解决一些特定的场景的耦合问题,以达到对修改封闭,对扩展开放的效果。
六种模式,学习它们,提高自己!除了这六种,还有几种比较常用的,接下来会继续思考,继续熟悉