设计模式(Design pattern)是一套被反复使用、多数人知晓的、经过分类编目的、代码设计经验的总结。使用设计模式是为了可重用代码、让代码更容易被他人理解、保证代码可靠性。毫无疑问,设计模式于己于他人于系统都是多赢的,设计模式使代码编制真正工程化,设计模式是软件工程的基石脉络,如同大厦的结构一样。
GoF(“四人帮”,又称Gang of Four,即Erich Gamma, Richard Helm,Ralph Johnson & John Vlissides四人)的《设计模式》,原名《Design Patterns:Elements of Reusable Object-Oriented Software》(1995年出版,出版社:Addison Wesly Longman.Inc),第一次将设计模式提升到理论高度,并将之规范化。该书提出了23种基本设计模式。时至今日,在可复用面向对象软件的发展过程中,新的设计模式仍然不断出现。
为什么要提倡"Design Pattern"呢?
根本原因是为了代码复用,增加可维护性。那么怎么才能实现代码复用呢?面向对象有几个原则:开闭原则(OpenClosed Principle,OCP)、里氏代换原则(Liskov Substitution Principle,LSP)、依赖倒转原则(Dependency Inversion Principle,DIP)、接口隔离原则(Interface Segregation Principle,ISP)、合成/聚合复用原则(Composite/Aggregate Reuse Principle,CARP)、最小知识原则(Principle of Least Knowledge,PLK,也叫迪米特法则)。开闭原则具有理想主义的色彩,它是面向对象设计的终极目标。其他几条,则可以看做是开闭原则的实现方法。
开闭原则
此原则是由"BertrandMeyer"提出的。原文是:"Software entities should beopen for extension,but closed for modification"。就是说模块应对扩展开放,而对修改关闭。模块应尽量在不修改原(是"原",指原来的代码)代码的情况下进行扩展。那么怎么扩展呢?我们看工厂模式"factorypattern":假设中关村有一个卖盗版盘和毛片的小子,我们给他设计一"光盘销售管理软件"。我们应该先设计一"光盘"接口
public interface 关盘{
void 卖();
}
而盗版盘和毛片是实现光盘接口的类。小子通过"DiscFactory"来管理这些光盘。代码为:
public class DiscFactory{
public static 光盘 getDisc(String name){
//return (光盘)Class.forName(name).getInstance();
return (光盘)Class.forName(name).newInstance();
}
有人要买盗版盘,怎么实现呢?
public class 小子{
public static void main(String[]args){
光盘d=DiscFactory.getDisc("盗版盘");
d.卖();
}
}
如果有一天,这小子良心发现了,开始卖正版软件。没关系,我们只要再创建一个是实现"光盘"的类"正版软件"就可以了。不需要修改原结构和代码。怎么样?对扩展开放,对修改关闭。"开-闭原则"
里氏代换原则
里氏代换原则是由"BarbaraLiskov"提出的。如果调用的是父类的话,那么换成子类也完全可以运行。比如:
光盘 d=new 盗版盘();
d.卖();
现在要将"盗版盘"类改为"毛片"类,没问题,完全可以运行。Java编译程序会检查程序是否符合里氏代换原则。还记得java继承的一个原则吗?子类override方法的访问权限不能小于父类对应方法的访问权限。比如"光盘"中的方法"卖"访问权限是"public",那么"盗版盘"和"毛片"中的"卖"方法就不能是protected或private,编译不能通过。为什么要这样呢?你想啊:如果"盗版盘"的"卖"方法是private。那么下面这段代码就不能执行了:
光盘 d=new 盗版盘();
d.卖();
可以说:里氏代换原则是继承复用的一个基础。
依赖倒转原则
抽象不应该依赖于细节,细节应当依赖于抽象。
要针对接口编程,而不是针对实现编程。
传递参数,或者在组合聚合关系中,尽量引用层次高的类。
主要是在构造对象时可以动态的创建各种具体对象,当然如果一些具体类比较稳定,就不必在弄一个抽象类做它的父类,这样有画蛇添足的感觉
接口隔离原则
定制服务的例子,每一个接口应该是一种角色,不多不少,不干不该干的事,该干的事都要干
合成/聚合复用原则(Composite/Aggregate Reuse Principle ,CARP)经常又叫做合成复用原则。合成/聚合复用原则就是在一个新的对象里面使用一些已有的对象,使之成为新对象的一部分;新的对象通过向这些对象的委派达到复用已有功能的目的。它的设计原则是;要尽量使用合成/聚合,尽量不要使用继承。
就是说要少用继承,多用合成关系来实现。我曾经这样写过程序:有几个类要与数据库打交道,就写了一个数据库操作的类,然后别的跟数据库打交道的类都继承这个。结果后来,我修改了数据库操作类的一个方法,各个类都需要改动。"牵一发而动全身"!面向对象是要把波动限制在尽量小的范围。
当你需要复用子类时,实现上的依赖性就会产生一些问题。如果继承下来的实现不适合解决新的问题,则父类必须重写或被其他更适合的类替换。这种依赖关系限制了灵活性并最终限制了复用性。一个可用的解决方法就是只继承抽象类,因为抽象类通常提供较少的实现。
在Java中,应尽量针对Interface编程,而非实现类。这样,更换子类不会影响调用它方法的代码。要让各个类尽可能少的跟别人联系,"不要与陌生人说话"。这样,城门失火,才不至于殃及池鱼。扩展性和维护性才能提高
理解了这些原则,再看设计模式,只是在具体问题上怎么实现这些原则而已。张无忌学太极拳,忘记了所有招式,打倒了"玄冥二老",所谓"心中无招"。设计模式可谓招数,如果先学通了各种模式,又忘掉了所有模式而随心所欲,可谓OO之最高境界。呵呵,搞笑,搞笑!(JR)
也叫迪米特法则。不要和陌生人说话。
一:单例模式
Singleton模式主要作用是保证在Java应用程序中,一个类Class只有一个实例存在。在很多操作中,比如建立目录数据库连接都需要这样的单线程操作。还有, singleton能够被状态化; 这样,多个单态类在一起就可以作为一个状态仓库一样向外提供服务,比如,你要论坛中的帖子计数器,每次浏览一次需要计数,单态类能否保持住这个计数,并且能synchronize的安全自动加1,如果你要把这个数字永久保存到数据库,你可以在不修改单态接口的情况下方便的做到。
另外方面,Singleton也能够被无状态化。提供工具性质的功能,Singleton模式就为我们提供了这样实现的可能。使用Singleton 的好处还在于可以节省内存,因为它限制了实例的个数,有利于Java垃圾回收(garbage collection)。我们常常看到工厂模式中类装入器(class loader)中也用Singleton模式实现的,因为被装入的类实际也属于资源。
示例一:
public classSingleton {
privateSingleton(){}
//在自己内部定义自己一个实例,是不是很奇怪?
//注意这是private 只供内部调用
private staticSingleton instance = new Singleton();
//这里提供了一个供外部访问本class的静态方法,可以直接访问
public staticSingleton getInstance() {
returninstance;
}
}
示例二:
public classSingleton {
private staticSingleton instance = null;
public staticsynchronized Singleton getInstance() {
//这个方法比上面有所改进,不用每次都进行生成对象,只是第一次
//使用时生成实例,提高了效率!
if(instance==null)
instance=new Singleton();
returninstance; }
}
使用 Singleton.getInstance()可以访问单态类。
二:工厂模式
工厂模式是一种创建性模式,它定义了一个创建对象的接口,但是却让子类来决定具体实例化哪一个类.当一个类无法预料要创建哪种类的对象或是一个类需要由子类来指定创建的对象时我们就需要用到工厂模式了.简单说来工厂模式可以根据不同的条件产生不同的实例,当然这些不同的实例通常是属于相同的类型。工厂模式把创建这些实例的具体过程封装起来了,简化了客户端的应用,也改善了程序的扩展性,使得将来可以做最小的改动就可以加入新的待创建的类. 通常我们将工厂模式作为一种标准的创建对象的方法,当发现需要更多的灵活性的时候,就开始考虑向其它创建型模式转化。
工厂模式在《Java与模式》中分为三类:
1)简单工厂模式(Simple Factory)
2)工厂方法模式(Factory Method)
3)抽象工厂模式(Abstract Factory)
简单工厂模式
简单工厂模式又称静态工厂方法模式。重命名上就可以看出这个模式一定很简单。它存在的目的很简单:定义一个用于创建对象的接口。
先来看看它的组成:
1) 工厂类角色:这是本模式的核心,含有一定的商业逻辑和判断逻辑。在java中它往往由一个具体类实现。
2) 抽象产品角色:它一般是具体产品继承的父类或者实现的接口。在java中由接口或者抽象类来实现。
3) 具体产品角色:工厂类所创建的对象就是此角色的实例。在java中由一个具体类实现。
类图来清晰的表示下的它们之间的关系
//抽象产品角色
public interface Car{
public void drive();
}
//具体产品角色
public class Benz implements Car{
public void drive() {
System.out.println("Driving Benz ");
}
}
public class Bmw implements Car{
public void drive() {
System.out.println("Driving Bmw ");
}
}
。。。(奥迪我就不写了:P)
//工厂类角色
public class Driver{
//工厂方法.注意 返回类型为抽象产品角色
public static Car driverCar(Strings)throws Exception {
//判断逻辑,返回具体的产品角色给Client
if(s.equalsIgnoreCase("Benz"))
returnnew Benz();
elseif(s.equalsIgnoreCase("Bmw"))
returnnew Bmw();
......
elsethrow new Exception();
。。。
。。。
//欢迎暴发户出场......
public class Magnate{
public static void main(String[]args){
try{
//告诉司机我今天坐奔驰
Carcar = Driver.driverCar("benz");
//下命令:开车
car.drive();
。。。
程序中各个类的关系表达如下:
工厂方法模式
工厂方法模式去掉了简单工厂模式中工厂方法的静态属性,使得它可以被子类继承。这样在简单工厂模式里集中在工厂方法上的压力可以由工厂方法模式里不同的工厂子类来分担。
你应该大致猜出了工厂方法模式的结构,来看下它的组成:
1) 抽象工厂角色: 这是工厂方法模式的核心,它与应用程序无关。是具体工厂角色必须实现的接口或者必须继承的父类。在java中它由抽象类或者接口来实现。
2) 具体工厂角色:它含有和具体业务逻辑有关的代码。由应用程序调用以创建对应的具体产品的对象。
3) 抽象产品角色:它是具体产品继承的父类或者是实现的接口。在java中一般有抽象类或者接口来实现。
4) 具体产品角色:具体工厂角色所创建的对象就是此角色的实例。在java中由具体的类来实现。
类图来清晰的表示下的它们之间的关系
//抽象产品角色
public interface Car{
public void drive();
}
//具体产品角色
public class Benz implements Car{
public void drive() {
System.out.println("Driving Benz ");
}
}
public class Bmw implements Car{
public void drive() {
System.out.println("Driving Bmw ");
}
//抽象工厂角色
public interface Driver{
public Car driverCar();
}
//具体工厂角色
public class BenzDriver implements Driver{
public Car driverCar(){
returnnew Benz();
}
}
public class BmwDriver implements Driver{
public CardriverCar() {
return new Bmw();
}
}
}
public class Magnate
{
publicstatic void main(String[] args)
{
try{
Driverdriver = new BenzDriver();
Carcar = driver.driverCar();
car.drive();
}
……
}
抽象工厂模式
类图来清晰的表示下的它们之间的关系
可以说,抽象工厂模式和工厂方法模式的区别就在于需要创建对象的复杂程度上。而且抽象工厂模式是三个里面最为抽象、最具一般性的
系统中有多个产品族,而系统一次只可能消费其中一族产品。
三:观察者模式
在一对多依赖的对象关系中,如果这个'一'对象状态发生了变化,那么它所有依赖的'多'对象都应该被通知,然后做相应的变化,这就是观察者模式.就如同'多'对象一直在观察'一'对象的状态变化一样.
在观察者模式中最重要的俩个对象分别是:Observable和Observer对象.它们的关系可总结如下:
1. Observable和Observer对象是一对多的关系,也就是说一旦Observable对象状态变化,它就要负责通知所有和它有关系的Observer对象,然后做相应的改变.
1. Observable对象不会主动去通知各个具体的Observer对象其状态发生了变化,而是提供一个注册接口供Observer对象使用,任何一个Observer对象如果想要被通知,则可以使用这个接口来注册.
3. 在Observable中有一个集合和一个状态控制开关,所有注册了通知的Observer对象会被保存在这个集合中.这个控制开关就是用来控制Observable是否发生了变化,一旦发生了变化,就通知所有的Observer对象更新状态.
在java api中分别提供了Observable对象:java.util.Observable和Observer接口:java.util.Observer.下面用实例来实现一下观察者模式:股票系统
所有的类如下:
StockData (Observable对象,也就是所股票数据发生了变化,它就要通知所有和它有关系的交易实体做相应的变化)
BigBuyer (Observer对象,实现了Observer接口)
TradingFool (Observer对象,实现了Observer接口)
StockQuote 测试类
在这个例子中一旦StockData对象的状态发生了变化,那BigBuyer和TradingFool都应该受到通知:
被观察者:StockData.java:
Java代码
package com.zhj.observer1;
import java.util.Observable;
public class StockData extendsObservable {
private String symbol;
private float close;
private float high;
private float low;
private long volume;
public void setStockData(Stringsymbol, float close, float high, float low, long volume){
this.symbol = symbol;
this.close = close;
this.high = high;
this.low = low;
this.volume = volume;
sendStockData();
}
//setChanged();说明有变更,notifyObservers();通知注入过的观察者调用update方法
public void sendStockData() {
setChanged();
notifyObservers();
}
public String getSymbol() {
return symbol;
}
public void setSymbol(Stringsymbol) {
this.symbol = symbol;
}
public float getClose() {
return close;
}
public void setClose(floatclose) {
this.close = close;
}
public float getHigh() {
return high;
}
public float getLow() {
return low;
}
public void setLow(float low) {
this.low = low;
}
public long getVolume() {
return volume;
}
public void setVolume(longvolume) {
this.volume = volume;
}
}
观察者BigBuyer.java:
Java代码
package com.zhj.observer1;
import java.text.DecimalFormat;
import java.text.DecimalFormatSymbols;
import java.util.Observable;
import java.util.Observer;
public class BigBuyer implements Observer {
private String symbol;
private float close;
private float high;
private float low;
private long volume;
public BigBuyer(Observable observable){
observable.addObserver(this);//注册关系
}
@Override
public void update(Observable observable, Objectarg) {
if(observable instanceof StockData) {
StockData stockData = (StockData) observable;
this.symbol = stockData.getSymbol();
this.close = stockData.getClose();
this.high = stockData.getHigh();
this.low = stockData.getLow();
this.volume = stockData.getVolume();
display();
}
}
public void display() {
DecimalFormatSymbols dfs = newDecimalFormatSymbols();
DecimalFormat volumeFormat = newDecimalFormat("###,###,###,###",dfs);
DecimalFormat priceFormat = newDecimalFormat("###.00",dfs);
System.out.println("Big Buyerreports... ");
System.out.println("\tThelastest stock quote for " + symbol + " is:");
System.out.println("\t$"+ priceFormat.format(close) + " per share (close).");
System.out.println("\t$"+ priceFormat.format(high) + " per share (high).");
System.out.println("\t$"+ priceFormat.format(low) + " per share (low).");
System.out.println("\t"+ volumeFormat.format(volume) + " shares traded.");
System.out.println();
}
}
客户端:测试端:StokeQuote.java
Java代码
package com.zhj.observer1;
public class StokeQuote {
public static void main(String[]args) {
System.out.println();
System.out.println("--Stock Quote Application --");
System.out.println();
StockData stockData = new StockData();
new BigBuyer(stockData);
stockData.setStockData("JUPM",16.10f,16.15f,15.34f,(long)481172);
}
}
在测试类中我们可以看到俩个Observer对象都注册了Observable对象,而当Observable对象发生改变时,这俩个Observable对象就会做相应的更新了, 运行结果如下:
Big Buyer reports...
The lastest stock quote for JUPM is:
$16.10 per share (close).
$16.15 per share (high)。
$15.34 per share (low).
481,172 shares traded.
四:建造者模式
当做一种事情的步骤是必不可少的。也就是说做这种事情,所有的步骤是不会少的。但是这些事情的具体做法的步骤实现又是不一样的。由每个个具体的对象去实现 。
比如做一道菜,放盐,油等这些都是不可少的,但是每个人放的量又不一样。
有的时候我们做的时候就可能会因为一件事情的步骤太多而忘记了做某个步骤,造成后面做出来的东西是不能用的。
建造者模式:它的原理就是像上面那样,为了避免忘记做某个步骤,我们把这些步骤都抽象出来到一个类里面,
然后每个具体的实现都不得不去实现这些步骤(也就是一个方法一样吧),具体方法的实现是由每个具体类自己去
做的,如果就是这样,我们就会发现这时候,等于就是这个吃菜的人吧,他要对做菜的人说,放盐,放油,呵呵,这
完全是没有必要的,所以在建造者模式当中还有一个重要的类,就是指挥者,它和客户和做菜人之间打交道,
客户对着它说做菜,然后它在叫某个具体做菜的人去做咯。
粗略代码:
//做菜所需要的步骤
public interface Cai {
public void fangyan(); //放盐
public void fangyou(); //放油
public void jiashui(); //加水,,,等等是做菜不可缺少的步骤
.......
}
//有具体叫张三去做这个菜,每个人才做,放盐,放油,加水等都是不一样的,但是都必须做这样的步骤
public class ZhangSan implements Cai {
public void fangyan() {
System.out.println("ZhangSanfangyan");
}
public void fangyou() {
System.out.println("ZhangSanfangyou");
}
public void jiashui() {
System.out.println("ZhangJiashui");
}
}
//有具体叫李四去做这个菜
public class LiSi implements Cai {
public void fangyan() {
System.out.println("LiSifangyan");
}
public void fangyou() {
System.out.println("LiSifangyou");
}
public void jiashui() {
System.out.println("LiSiJiashui");
}
}
//客户来了,不是说客户交张三去做菜,然后放盐放油,放水,都客户叫,这个指挥者做的事情
public class Director {
Cai cai;
Public Director(Cai cai) {
this.cai = cai;
}
public void zuocai() {
cai.fangyan();
cai.fangyou();
cai.fangshui();
}
}
//客户调用
public class test {
public static void main(String[] args) {
Cai cai = new ZhangSan();//张三做菜
Director d = new Director(cai); //指挥者
d.zuocai();//我要指挥者弄菜,其实指挥者叫张三弄菜去了。
}
}
五:适配器模式
主要作用是将一个类的接口转换成客户希望的另外一个接口。适配器模式使得原本由于接口不兼容而不能一起工作的那些类可以一起工作
以下情况使用适配器模式
• 你想使用一个已经存在的类,而它的接口不符合你的需求。
• 你想创建一个可以复用的类,该类可以与其他不相关的类或不可预见的类(即那些接口可能不一定兼容的类)协同工作。
其实现方式主要有两种:
类适配器
对象适配器
具体的实现代码如下:
类适配器
package com.visionsky.DesignPattern;
interface Target {
void Request();
}
class Adaptee {
void SpecificRequst() {
System.out.println("Adaptee's SpecificRequst");
}
}
class Adapter extends Adaptee implements Target
{
@Override
public void Request() {
System.out.println("Adapter's Request");
super.SpecificRequst();
}
}
public class AdapterDemo {
/**
* @param args
*/
public static void main(String[] args) {
// TODO Auto-generated method stub
Target t=new Adapter();
t.Request();
}
}
对象适配器
package com.visionsky.DesignPattern;
interface Target {
void Request();
}
class Adaptee {
void SpecificRequst() {
System.out.println("Adaptee's SpecificRequst");
}
}
class Adapter implements Target
{
private Adaptee adaptee;
public Adapter()
{
this.adaptee=new Adaptee();
}
@Override
public void Request() {
System.out.println("Adapter's Request");
adaptee.SpecificRequst();
}
}
public class AdapterDemo {
/**
* @param args
*/
public static void main(String[] args) {
// TODO Auto-generated method stub
Target t=new Adapter();
t.Request();
}
}
生活中的例子:
在生活中最简单的例子就是电源适配器,如手机适配器,我们的家用电源是220V的,但是,对于手机来说,根本大不了这么多,所以,在这种情况下,需要电源适配器来为我们提供适合手机的电压。
package com.visionsky.DesignPattern;
public class AdaterDemoInLife {
/**
* @param args
*/
public static void main(String[] args) {
// TODO Auto-generated method stub
MobilePowerAdapter mpa=new MobilePowerAdapter();
mpa.GetPower10V();
}
}
interface ITarget {
int GetPower10V();
}
class Power {
int GetPower220V() {
return 220;
}
}
class MobilePowerAdapter implements ITarget
{
private Power power;
public MobilePowerAdapter( ) {
this.power = new Power();
}
@Override
public int GetPower10V() {
// TODO Auto-generated method stub
power.GetPower220V();
//将220v转变成10v,具体做法就不写了, 大家明白就行
return 10;
}
}
六:DAO模式
数据访问对象(data access object,DAO)模式将数据访问逻辑抽象为特殊的资源,也就是说将系统资源的接口从其底层访问机制中隔离出来;通过将数据访问的调用打包,数据访问对象可以促进对于不同数据库类型和模式的数据访问。
这种模式出现的背景在于数据访问的逻辑极大程度上取决于数据存储的格式,比如说关系型数据库、面向对象数据库、磁盘文件等。
基于以上所讨论的问题,开发人员开始采用数据访问对象的方法。数据访问对象实际上就是包含对于所有数据访问逻辑的对象,并管理着对于数据源的连接,根据数据源的不同,数据访问对象实现了不同的访问机制,这里所说的数据源可以是持久性存储介质,如关系型数据库,也可以是外部服务,如B2B 的数据交换;不仅是用户,而且包括应用系统中的其他组件,也可以使用数据访问对象所提供的数据访问接口,数据访问对象将数据源的物理实现细节与其用户完全分离开来,并且在底层数据源变化的时候,数据访问对象向用户提供的接口是不会变化的;这种方法使应用系统使用数据访问对象时可以适应多种数据存储介质,总之,数据访问对象就是系统组件和数据源中间的适配器。
当底层的数据存储不会轻易改变的时候,开发人员可以采取这种方法来实现相应的,
数据访问对象,下图是这种方法的类图。
当底层的数据存储可能会变化的时候,开发人员可以采用抽象代理的方法来实现数
据访问对象;抽象代理的方法会创建一些虚拟的数据访问对象代理和各种类型的实际数据
访问对象代理,每种对象对应一种持久性存储介质的实现,一旦组件得到这些代理,就可以利用来创建需要使用的数据访问对象。下面是这种方法的类图。