设计模式;
一个程序员对设计模式的理解:
“不懂”为什么要把很简单的东西搞得那么复杂。后来随着软件开发经验的增加才开始明白我所看到的“复杂”恰恰就是设计模式的精髓所在,我所理解的“简单”就是一把钥匙开一把锁的模式,目的仅仅是着眼于解决现在的问题,而设计模式的“复杂”就在于它是要构造一个“万能钥匙”,目的是提出一种对所有锁的开锁方案。在真正理解设计模式之前我一直在编写“简单”的代码.
这个“简单”不是功能的简单,而是设计的简单。简单的设计意味着缺少灵活性,代码很钢硬,只在这个项目里有用,拿到其它的项目中就是垃圾,我将其称之为“一次性代码”。
-->要使代码可被反复使用,请用'设计模式'对你的代码进行设计.
很多我所认识的程序员在接触到设计模式之后,都有一种相见恨晚的感觉,有人形容学习了设计模式之后感觉自己好像已经脱胎换骨,达到了新的境界,还有人甚至把是否了解设计模式作为程序员划分水平的标准。
我们也不能陷入模式的陷阱,为了使用模式而去套模式,那样会陷入形式主义。我们在使用模式的时候,一定要注意模式的意图(intent),而不 要过多的去关注模式的实现细节,因为这些实现细节在特定情况下,可能会发生一些改变。不要顽固地认为设计模式一书中的类图或实现代码就代表了模式本身。
设计原则:(重要)
1.逻辑代码独立到单独的方法中,注重封装性--易读,易复用。
不要在一个方法中,写下上百行的逻辑代码。把各小逻辑代码独立出来,写于其它方法中,易读其可重复调用。
2.写类,写方法,写功能时,应考虑其移植性,复用性:防止一次性代码!
是否可以拿到其它同类事物中应该?是否可以拿到其它系统中应该?
3.熟练运用继承的思想:
找出应用中相同之处,且不容易发生变化的东西,把它们抽取到抽象类中,让子类去继承它们;
继承的思想,也方便将自己的逻辑建立于别人的成果之上。如ImageField extends JTextField;
熟练运用接口的思想:
找出应用中可能需要变化之处,把它们独立出来,不要和那些不需要变化的代码混在一起。
把很简单的东西搞得那么复杂,一次性代码,设计模式优势的实例说明:(策略模式)
说明:
模拟鸭子游戏的应用程序,要求:游戏中会出现各种颜色外形的鸭子,一边游泳戏水,一边呱呱叫。
第一种方法:(一次性代码)
直接编写出各种鸭子的类:MallardDuck//野鸭,RedheadDuck//红头鸭,各类有三个方法:
quack():叫的方法
swim():游水的方法
display():外形的方法
第二种方法:运用继承的特性,将其中共同的部分提升出来,避免重复编程。
即:设计一个鸭子的超类(Superclass),并让各种鸭子继承这个超类。
public class Duck{ public void quack(){ //呱呱叫 System.out.println("呱呱叫"); } public void swim(){ //游泳 System.out.println(" 游泳"); } public abstratact void display(); /*因为外观不一样,让子类自己去决定了。*/ } |
对于它的子类只需简单的继承就可以了,并实现自己的display()方法。
//野鸭 public class MallardDuck extends Duck{ public void display(){ System.out.println("野鸭的颜色..."); } } //红头鸭 public class RedheadDuck extends Duck{ public void display(){ System.out.println("红头鸭的颜色..."); } } |
不幸的是,现在客户又提出了新的需求,想让鸭子飞起来。这个对于我们OO程序员,在简单不过了,在超类中在加一个方法就可以了。
public class Duck{ public void quack(){ //呱呱叫 System.out.println("呱呱叫"); } public void swim(){ //游泳 System.out.println(" 游泳"); } public abstract void display(); /*因为外观不一样,让子类自己去决定了。*/ public void fly(){ System.out.println("飞吧!鸭子"); } } |
对于不能飞的鸭子,在子类中只需简单的覆盖。
//残废鸭 public class DisabledDuck extends Duck{ public void display(){ System.out.println("残废鸭的颜色..."); } public void fly(){ //覆盖,变成什么事都不做。 } } |
其它会飞的鸭子不用覆盖。
这样所有的继承这个超类的鸭子都会fly了。但是问题又出来了,客户又提出有的鸭子会飞,有的不能飞。
>>>>>>点评:
对于上面的设计,你可能发现一些弊端,如果超类有新的特性,子类都必须变动,这是我们开发最不喜欢看到的,一个类变让另一个类也跟着变,这有点不符合OO设计了。这样很显然的耦合了一起。利用继承-->耦合度太高了.
第三种方法:用接口改进.
我们把容易引起变化的部分提取出来并封装之,来应付以后的变法。虽然代码量加大了,但可用性提高了,耦合度也降低了。
我们把Duck中的fly方法和quack提取出来。
public interface Flyable{ public void fly(); } public interface Quackable{ public void quack(); } 最后Duck的设计成为: public class Duck{ public void swim(){ //游泳 System.out.println(" 游泳"); } public abstract void display(); /*因为外观不一样,让子类自 己去决定了。*/ } 而MallardDuck,RedheadDuck,DisabledDuck 就可以写成为: //野鸭 public class MallardDuck extends Duck implements Flyable,Quackable{ public void display(){ System.out.println("野鸭的颜色..."); } public void fly(){ //实现该方法 } public void quack(){ //实现该方法 } } //红头鸭 public class RedheadDuck extends Duck implements Flyable,Quackable{ public void display(){ System.out.println("红头鸭的颜色..."); } public void fly(){ //实现该方法 } public void quack(){ //实现该方法 } } //残废鸭 只实现Quackable(能叫不能飞) public class DisabledDuck extends Duck implements Quackable{ public void display(){ System.out.println("残废鸭的颜色..."); } public void quack(){ //实现该方法 } } |
>>>>>>点评:
好处:
这样已设计,我们的程序就降低了它们之间的耦合。
不足:
Flyable和 Quackable接口一开始似乎还挺不错的,解决了问题(只有会飞到鸭子才实现 Flyable),但是Java接口不具有实现代码,所以实现接口无法达到代码的复用。
第四种方法:对上面各方式的总结:
继承的好处:让共同部分,可以复用.避免重复编程.
继承的不好:耦合性高.一旦超类添加一个新方法,子类都继承,拥有此方法,
若子类相当部分不实现此方法,则要进行大批量修改.
继承时,子类就不可继承其它类了.
接口的好处:解决了继承耦合性高的问题.
且可让实现类,继承或实现其它类或接口.
接口的不好:不能真正实现代码的复用.可用以下的策略模式来解决.
------------------------- strategy(策略模式) -------------------------
我们有一个设计原则:
找出应用中相同之处,且不容易发生变化的东西,把它们抽取到抽象类中,让子类去继承它们;
找出应用中可能需要变化之处,把它们独立出来,不要和那些不需要变化的代码混在一起。 -->important.
现在,为了要分开“变化和不变化的部分”,我们准备建立两组类(完全远离Duck类),一个是"fly"相关的,另一个
是“quack”相关的,每一组类将实现各自的动作。比方说,我们可能有一个类实现“呱呱叫”,另一个类实现“吱吱
叫”,还有一个类实现“安静”。
首先写两个接口。FlyBehavior(飞行行为)和QuackBehavior(叫的行为).
public interface FlyBehavior{ public void fly(); } public interface QuackBehavior{ public void quack(); } 我们在定义一些针对FlyBehavior的具体实现。 public class FlyWithWings implements FlyBehavior{ public void fly(){ //实现了所有有翅膀的鸭子飞行行为。 } } public class FlyNoWay implements FlyBehavior{ public void fly(){ //什么都不做,不会飞 } } 针对QuackBehavior的几种具体实现。 public class Quack implements QuackBehavior{ public void quack(){ //实现呱呱叫的鸭子 } } public class Squeak implements QuackBehavior{ public void quack(){ //实现吱吱叫的鸭子 } } public class MuteQuack implements QuackBehavior{ public void quack(){ //什么都不做,不会叫 } } |
点评一:
这样的设计,可以让飞行和呱呱叫的动作被其他的对象复用,因为这些行为已经与鸭子类无关了。而我们增加一些新的行为,不会影响到既有的行为类,也不会影响“使用”到飞行行为的鸭子类。
最后我们看看Duck 如何设计。
public class Duck{ --------->在抽象类中,声明各接口,定义各接口对应的方法. FlyBehavior flyBehavior;//接口 QuackBehavior quackBehavior;//接口 public Duck(){} public abstract void display(); public void swim(){ //实现游泳的行为 } public void performFly(){ flyBehavior.fly(); -->由于是接口,会根据继承类实现的方式,而调用相应的方法. } public void performQuack(){ quackBehavior.quack();(); } } |
看看MallardDuck如何实现。
----->通过构造方法,生成'飞','叫'具体实现类的实例,从而指定'飞','叫'的具体属性
public class MallardDuck extends Duck{ public MallardDuck { flyBehavior = new FlyWithWings (); quackBehavior = new Quack(); //因为MallardDuck 继承了Duck,所有具有flyBehavior 与quackBehavior 实例变量} public void display(){ //实现 } } |
这样就满足了即可以飞,又可以叫,同时展现自己的颜色了。
这样的设计我们可以看到是把flyBehavior ,quackBehavior 的实例化写在子类了。我们还可以动态的来决定。
我们只需在Duck中加上两个方法。
在构造方法中对属性进行赋值与用属性的setter的区别:
构造方法中对属性进行赋值:固定,不可变;
用属性的setter,可以在实例化对象后,动态的变化,比较灵活。
public class Duck{ FlyBehavior flyBehavior;//接口 QuackBehavior quackBehavior;//接口 public void setFlyBehavior(FlyBehavior flyBehavior){ this.flyBehavior = flyBehavior; } public void setQuackBehavior(QuackBehavior quackBehavior { this.quackBehavior= quackBehavior; } } |
------------------------- static Factory Method(静态工厂) -------------------------
(1)在设计模式中,Factory Method也是比较简单的一个,但应用非常广泛,EJB,RMI,COM,CORBA,Swing中都可以看到此模式的影子,它是最重要的模式之一.在很多地方我们都会看到xxxFactory这样命名的类.
(2)基本概念:
FactoryMethod是一种创建性模式,它定义了一个创建对象的接口,但是却让子类来决定具体实例化哪一个类.
通常我们将Factory Method作为一种标准的创建对象的方法。
应用方面:
当一个类无法预料要创建哪种类的对象或是一个类需要由子类来指定创建的对象时我们就需要用到Factory Method 模式了.
-------------------------------- singelton(单例模式) --------------------------------
基本概念:
Singleton 是一种创建性模型,它用来确保只产生一个实例,并提供一个访问它的全局访问点.对一些类来说,保证只有一个实例是很重要的,比如有的时候,数据库连接或 Socket 连接要受到一定的限制,必须保持同一时间只能有一个连接的存在.
运用:
在于使用static变量;
创建类对象,一般是在构造方法中,或用一个方法来创建类对象。在这里方法中,加对相应的判断即可。
单态模式与共享模式的区别:
单态模式与共享模式都是让类的实例是唯一的。
但单态模式的实现方式是:
在类的内部.即在构造方法中,或静态的getInstace方法中,进行判断,若实例存在,则直接返回,不进行创建;
共享模式的实现方式是:
每次要用到此实例时,先去此hashtable中获取,若获取为空,则生成实例,且将类的实例放在一人hashtable中,若获取不为空,则直接用此实例。
(2)实例一:
public class Singleton { private static Singleton s; public static Singleton getInstance() { if (s == null) s = new Singleton(); return s; } } // 测试类 class singletonTest { public static void main(String[] args) { Singleton s1 = Singleton.getInstance(); Singleton s2 = Singleton.getInstance(); if (s1==s2) System.out.println("s1 is the same instance with s2"); else System.out.println("s1 is not the same instance with s2"); } } |
singletonTest运行结果是:
s1 is the same instance with s2
(3)实例二:
class Singleton { static boolean instance_flag = false; // true if 1 instance public Singleton() { if (instance_flag) throw new SingletonException("Only one instance allowed"); else instance_flag = true; // set flag for 1 instance } } |
-------------------------------- 观察者模式(Observer) --------------------------------
(1)基本概念:
观察者模式属于行为型模式,其意图是定义对象间的一种一对多的依赖关系,当一个对象的状态发生改变时,所有依赖于它的对象都得到通知并被自动更新。
这一个模式的关键对象是目标(Subject)和观察者(Observer)。一个目标可以有任意数目的依赖它的观察者,一旦目标的状态发生改变,所有的观察者都得到通知,作为对这个通知的响应,每个观察者都将查询目标以使其状态与目标的状态同步。
适用场景:
观察者模式,用于存在一对多依赖关系的对象间,当被依赖者变化时,通知依赖者全部进行更新。因此,被依赖者,应该有添加/删除依赖者的方法,且可以将添加的依赖者放到一个容器中;且有一个方法去通知依赖者进行更新。
(2)思想:
(一)建立目标(subject)与观察者(observer)接口:
目标(subject)接口:
建立一个注册观察者对象的接口; public void attach(Observer o);
建立一个删除观察者对象的接口; public void detach(Observer o);
建立一个当目标状态发生改变时,发布通知给观察者对象的接口; public void notice();
观察者(observer)接口:
建立一个当收到目标通知后的更新接口: public void update();
(3)实例:
老师又电话号码,学生需要知道老师的电话号码以便于在合时的时候拨打,在这样的组合中,老师就是一个被观察者(Subject),学生就是需要知道信息的观察者,当老师的电话号码发生改变时,学生得到通知,并更新相应的电话记录。
具体实例如下:
Subject代码: public interface Subject{ public void attach(Observer o); public void detach(Observer o); public void notice(); } Observer代码: public interface Observer{ public void update(); } Teacher代码; import java.util.Vector; public class Teacher implements Subject{ private String phone; private Vector students; public Teacher(){ phone = ""; students = new Vector(); } public void attach(Observer o){ students.add(o); } public void detach(Observer o){ students.remove(o); } public void notice(){ for(int i=0;i<students.size();i++) ((Observer)students.get(i)).update(); } public void setPhone(String phone){ this.phone = phone; notice(); --关键 } public String getPhone(){ return phone; } } Student代码: public class Student implements Observer{ private String name; private String phone; private Teacher teacher; public Student(String name,Teacher t){ this.name = name; teacher = t; } public void show(){ System.out.println("Name:"+name+"\nTeacher'sphone:"+phone); } public void update(){ phone = teacher.getPhone(); } } Client代码: package observer; import java.util.Vector; public class Client{ -->可以只定义目标者,观察者,另外的vector,只为了输入结果. public static void main(String[] args){ Vector students = new Vector(); Teacher t = new Teacher(); for(int i= 0 ;i<10;i++){ Student st = new Student("lili"+i,t); students.add(st); t.attach(st); } t.setPhone("88803807"); for(int i=0;i<10;i++) ((Student)students.get(i)).show(); t.setPhone("88808880"); for(int i=0;i<10;i++) ((Student)students.get(i)).show(); } } |
总结:Observer模式的最知名的应用是在MVC结构,Observer模式可以很好的应用在文档和图表程序的制作中。
(1)基本概念:
迭代器模式属于行为型模式,其意图是提供一种方法顺序访问一个聚合对象中得各个元素,而又不需要暴露该对象的内部表示。
至少可以历遍first,next,previous,last,isOver,或是历遍选择符合某种条件的子元素.
(2)结构:
由一个接口与一个实现类组成.
接口: 主要是定义各历遍的方法.
实现类: 需要一个计算点private int current=0 ; 以及一个容器Vector,来存在原来的进行历遍的一团东西;再对接口方法进行实现.
(3)实例:
Iterator接口:
package iterator;
public interface Iterator{
/*
Item:即是集合中的各对象的类型.若为String,即把所有的ITEM改为String,若为其它自定义的类,则改为各自定义的类的接口,或类. --->important.
*/ public Item first(); public Item next(); public boolean isDone(); public Item currentItem(); } Controller类实现了Iterator接口。 package iterator; import java.util.Vector; public class Controller implements Iterator{ private int current =0; Vector channel; public Controller(Vector v){ channel = v; } public Item first(){ current = 0; return (Item)channel.get(current); } public Item next(){ current ++; return (Item)channel.get(current); } public Item currentItem(){ return (Item)channel.get(current); } public boolean isDone(){ return current>= channel.size()-1; } } Television接口: package iterator; import java.util.Vector; public interface Television{ public Iterator createIterator(); } HaierTV类实现了Television接口。 package iterator; import java.util.Vector; public class HaierTV implements Television{ ---对象 private Vector channel; public HaierTV(){ channel = new Vector(); channel.addElement(new Item("channel 1")); --各元素,用VECTOR存放 channel.addElement(new Item("channel 2")); channel.addElement(new Item("channel 3")); channel.addElement(new Item("channel 4")); channel.addElement(new Item("channel 5")); channel.addElement(new Item("channel 6")); channel.addElement(new Item("channel 7")); } public Iterator createIterator(){ return new Controller(channel); --把这个VECTOR放到迭代器中构造方法中去 } } Client客户端: package iterator; public class Client{ public static void main(String[] args){ Television tv = new HaierTV(); Iterator it =tv.createIterator(); System.out.println(it.first().getName()); while(!it.isDone()){ System.out.println(it.next().getName()); } } } Item类的接口: package iterator; public class Item{ private String name; public Item(String aName){ name = aName; } public String getName(){ return name; } } |
------------------------------ 外观模式(Facade) -------------------------------
(1)外观模式属于结构型模式,Facade模式定义了一个高层接口,这个接口使得这一子系统更加容易使用。外观模式的主要用途就是为子系统的复杂处理过程提供方便的调用方法,使得子系统更加容易被使用。
-->将复杂的过程包含在里面,提供一个简单的应用接口即可.
(2)例如在一个泡茶的过程中,需要作如下的工作:烧开水,准备茶叶,把茶叶放在被子里,把烧开的水放到茶杯中,只有经过这些过程之后才能泡出好的茶叶来。这是一个常用的步骤,80%的泡茶步骤都是这个样子的,可以把这些动作串联起来,形成一个整体的步骤.如下例的MakeACuppa(),使用了facade的模式,这样在调用步方法时就比较方便。这便是外观模式,里面的细节被屏蔽掉了。
public class TeaCup{.....} public class TeaBag{.....} public class Water{.....} public class FacadeCuppaMaker{ private boolean TeaBagIsSteeped; public FacadeCuppaMaker(){ System.out.println("FacadeCuppaMaker 准备好冲茶了"); } public TeaCup makeACuppa(){ TeaCup cup = new TeaCup(); TeaBag teaBag= new TeaBag(); Water water = new Water(); cup.addFacadeTeaBag(teaBag); water.boilFacadeWater(); cup.addFacadeWater(water); cup.steepTeaBag(); return cup; } } |
------------------------------ 适配器模式(adapter) -------------------------------
(1)适配器模式的意图是将一个已存在的类/接口进行复用,将其转换/具体化成客户希望的另外的一个类/接口。
(2)如何实例复用:
将要进行复用的类,放到目标类的构造方法中,进行实例化,然后在目标类的相应方法中,进行调用,修改原来方法中的参数,或添加相应的逻辑。即复用了已有类的原来方法。
要被复用的类:
public class Adaptee{ public long getPower(long base,long exp){ long result=1; for(int i=0;i<exp;i++) result*=base; return result; } } |
目标类:--也可直接实现,不用接口。
public interface Target{ public long get2Power(long exp); } public class Adapter implements Target{ private Adaptee pt; public Adapter(){ pt = new Adaptee(); } public long get2Power(long exp){ return pt.getPower(2,exp); ---修改原来方法中的参数, } } |
(3)又如:在SCM中添加的方法:
已有接口:
public boolean updateRecordStates(Double recordId,Double tableNameMapping,int state,boolean
subRecordUpdate) throws RemoteException;
已有实现类:
public boolean updateRecordStates(Double recordId,Double tableNameMapping,int state,boolean subRecordUpdate) throws RemoteException { return moveTable.updateRecordStates(recordId,tableNameMapping,state,subRecordUpdate); } |
若采用适配器模式:
接口:
public boolean updateStatesAdapterForSelfPanel(Double recordId,Double tableNameMapping,int state)
throws RemoteException;
实现类:
public boolean updateStatesAdapterForSelfPanel(Double recordId,Double tableNameMapping,int state) throws RemoteException { return this.updateRecordStates(recordId,tableNameMapping,state,false); } |
相关文章:
java常用的设计模式(2)