设计模式「Design Pattern」可谓软件设计思想精髓的集中体现,是前人在大量实践中的总结和提炼,网络上相关的资料多如牛毛,好像没有炒冷饭的必要,但这不代表自己能够吸收并且在工程项目中灵活运用,还是自己总结来的比较实在。
在总结常用的设计模式之前,有必要对设计模式的几条基本原则进行充分的理解:
开闭原则(Open Close Principle)
简单说就是「对修改关闭,对扩展开放」,怎么理解:在设计项目的一个模块(比如根据账户类别查询账户信息)时,假设我们已经实现根据「往来户」和「内部户」这两种账户类别查询账户信息,后续当我们新增账户类别时,我们并不希望去修改原有的业务逻辑,而是针对新增的账户类别查询子模块实现热插拔的效果。我们往往需要借助接口和抽象类来实现这样的效果。
里氏代换原则(Liskov Substitution Principle)
里式代换原则是面向对象语言设计的基本原则之一,是软件功能单元复用的基础。简单的说,该原则表明了:「所有基类可以出现的地方,子类一定可以出现」。是对抽象的「开闭原则」的具体实现,在之后模式的总结中,会大量看到里式替换原则的运用。
依赖倒转原则(Dependence Inversion Principle)
该原则更像是一种方法论,核心内容是:针对接口编程,依赖于抽象的接口而不是具体的实现。
接口隔离原则(Interface Segregation Principle)
该原则提倡我们使用多个接口,而不是单个,以便于接口与接口之间的隔离,降低接口之间的耦合度。
迪米特法则(最少知道原则)(Demeter Principle)
一个实体应当尽量少的与其他实体之间发生相互作用,使得系统功能模块相对独立。
合成复用原则(Composite Reuse Principle)
尽量使用合成/聚合的方式,而不是继承。
在介绍完设计模式的六条基本原则之后,下面来具体说说常用的设计模式,被前人归纳总结出来的设计模式大约有23种(最著名的就是那部惟妙惟肖的GoF巨头了),可以分成以下三类:
- 创建型模式(共五种):工厂方法模式、抽象工厂模式、单例模式、建造者模式、原型模式;
- 结构型模式(共七种):适配器模式、装饰器模式、代理模式、外观模式、桥接模式、组合模式、享元模式;
- 行为型模式(共十一种):策略模式、模板方法模式、观察者模式、迭代子模式、责任链模式、命令模式、备忘录模式、状态模式、访问者模式、中介者模式、解释器模式;
模式何其多,想必在实际工程项目中能同时使用这些模式的可能性真的很小了吧,实践的少,往往导致半瓶子醋的下场,所以还是对比较常用的模式进行总结:
[1]抽象工厂模式
示例代码:
// 公共接口
public interface Sender {
public void Send();
}
// 三个实现类:
public class MailSender implements Sender {
@Override
public void Send() {
System.out.println("this is mailsender!");
}
}
public class SmsSender implements Sender {
@Override
public void Send() {
System.out.println("this is sms sender!");
}
}
public class VoiceSender implements Sender {
@Override
public void Send() {
System.out.println("this is voice sender!");
}
}
// 还可以扩展许多具体的sender类型
// 抽象工厂的公共接口:
public interface Provider {
public Sender produce();
}
// 三个具体工厂类,对每个具体类型的sender提供工厂方法
public class SendMailFactory implements Provider {
@Override
public Sender produce(){
return new MailSender();
}
}
public class SendSmsFactory implements Provider{
@Override
public Sender produce() {
return new SmsSender();
}
}
public class SendVoiceFactory implements Provider{
@Override
public Sender produce() {
return new VoiceSender();
}
}
// 测试类
public class Test {
public static void main(String[] args) {
Provider provider = new SendVoiceFactory();
Sender sender = provider.produce();
sender.Send();
}
}
使用抽象工厂模式的好处显而易见,每当新增一种具体类型的sender时,我们只需要实现Sender接口,同时提供配套的工厂方法,而无需修改原有的业务逻辑,方便扩展。
[2]单例模式
单列模式在实际的工程项目中使用更加频繁,之所以运用比较多,总结起来有这样显而易见的好处:减少内存开销和GC压力,因为单例对象在一个JVM中仅且存在一个,省去很多次new操作带来的系统开销,示例如下:
public class SingletonTest {
// 声明单例对象
private static SingletonTest instance = null;
private SingletonTest() {}
private static synchronized void syncInit() {
if (instance == null) {
instance = new SingletonTest();
}
}
public static SingletonTest getInstance() {
if (instance == null) {
syncInit();
}
return instance;
}
}
[3]适配器模式
转换某个类的接口,成为客户端期望的另一种接口表示,消除由于接口不适配引起的类兼容性问题。主要分成三类:类的适配器模式;对象的适配器模式和接口的适配器模式。
类的适配器模式示例如下:
// 待匹配接口method1所在的类Source
public class Source {
public void method1() {
System.out.println("this is original method!");
}
}
// 目标接口所在的类Targetable
public interface Targetable {
public void method1(); // 同类Source中的方法
public void method2(); // 新增方法
}
// 适配器类,继承Source类,实现Targetable接口
public class Adapter extends Source implements Targetable {
@Override
public void method2() {
System.out.println("this is the targetable method!");
}
}
// 测试类
public class AdapterTest {
public static void main(String[] args) {
Targetable target = new Adapter();
target.method1();
target.method2();
}
}
以上就完成了把类Source中的接口适配到Targetable类中的目的,下面再看对象的适配器模式,其实非常简单就是在适配器类中加入对适配原对象的引用,示例如下:
// 待匹配接口method1所在的类Source
public class Source {
public void method1() {
System.out.println("this is original method!");
}
}
// 目标接口所在的类Targetable
public interface Targetable {
public void method1(); // 同类Source中的方法
public void method2(); // 新增方法
}
// 适配器类,实现Targetable接口,加入对Source类的引用
public class Adapter implements Targetable {
private Source source;
public Adapter(Source source){
super();
this.source = source;
}
@Override
public void method1(){
source.method1(); // 调用Source类中的method1方法
}
@Override
public void method2() {
System.out.println("this is the targetable method!");
}
}
//测试类:
public class AdapterTest {
public static void main(String[] args) {
Source source = new Source()
Targetable targetable = new Adapter(source);
targetable.method1();
targetable.method2();
}
}
说明了对象的适配器模式,接着说说接口的适配器模式,有时我们定义一个接口,接口中包含了很多方法的声明,任何继承该接口的实现类必须都要实现接口中事先声明的方法,但真实环境下我们可能只是需要这个接口中的一个或者两个,不需要全部,这个时候我们就可以实现一个抽象类,该抽象类实现接口中声明的所有方法,其他类继承该抽象类,根据需要重写相应的方法即可,示例如下:
// 公共接口
public interface Sourceable {
public void method1();
public void method2();
}
// 抽象类,实现公共接口中的方法
public abstract class Wrapper implements Sourceable{
public void method1(){}
public void method2(){}
}
// 子类1,只重写method1
public class SourceSub1 extends Wrapper {
public void method1(){
System.out.println("the sourceable interface's first Sub1!");
}
}
// 子类2,重写method2
public class SourceSub2 extends Wrapper {
public void method2(){
System.out.println("the sourceable interface's second Sub2!");
}
}
// 测试类
public class WrapperTest {
public static void main(String[] args) {
Sourceable source1 = new SourceSub1();
Sourceable source2 = new SourceSub2();
source1.method1();
source1.method2();
source2.method1();
source2.method2();
}
}
[4]代理模式
代理模式理解起来相对比较简单,就是在调用者和被调用者之间,增加一个代理者的角色,调用者通过代理者调用目标方法,示例代码如下:
// 公用接口
public interface Sourceable {
public void method();
}
// 被调用者
public class Source implements Sourceable {
@Override
public void method() {
System.out.println("the original method!");
}
}
// 代理者
public class Proxy implements Sourceable {
// 对被调用者的引用
private Source source;
public Proxy(){
super();
this.source = new Source();
}
@Override
public void method() {
before();
source.method(); // 调用被调用者的方法
atfer();
}
private void atfer() {
System.out.println("after proxy!");
}
private void before() {
System.out.println("before proxy!");
}
}
// 测试类
public class ProxyTest {
public static void main(String[] args) {
Sourceable source = new Proxy();
source.method();
}
}
[5]观察者模式
观察者模式非常好理解,它表达的是类与类之间的关系,不涉及到继承,类似于RSS订阅。比如我们经常浏览一些技术博客,在这些站点上基本能看到RSS订阅源的图标,假设我们订阅了某个博客或者站点的RSS源,那么这些站点的内容一旦有更新,作为订阅者(观察者)的我们就马上能得到通知。示例代码如下:
/* 公用的观察者接口,包含一个内容更新通知的方法,
订阅源的内容发生更新时,可以调用update方法,把新内容通知给观察者 */
public interface Observer {
public void update();
}
// 具体的观察者1
public class Observer1 implements Observer {
@Override
public void update() {
System.out.println("observer1 has received!");
}
}
// 具体的观察者2
public class Observer2 implements Observer {
@Override
public void update() {
System.out.println("observer2 has received!");
}
}
// 订阅源主体的公用接口
public interface Subject {
public void add(Observer observer); // 增加观察者
public void del(Observer observer); // 删除观察者
public void notifyObservers(); // 通知所有观察者
public void operation();
}
// 实现公用接口所有方法的抽象类
public abstract class AbstractSubject implements Subject {
private Vector<Observer> vector = new Vector<Observer>();
@Override
public void add(Observer observer) {
vector.add(observer);
}
@Override
public void del(Observer observer) {
vector.remove(observer);
}
@Override
public void notifyObservers() {
Enumeration<Observer> enumo = vector.elements();
while(enumo.hasMoreElements()){
enumo.nextElement().update();
}
}
}
// 某个具体的订阅源
public class MySubject extends AbstractSubject {
@Override
public void operation() {
System.out.println("update self!");
notifyObservers();
}
}
// 测试类
public class ObserverTest {
public static void main(String[] args) {
Subject sub = new MySubject();
sub.add(new Observer1());
sub.add(new Observer2());
sub.operation();
}
}
结束语
以上总结了几种比较常用的设计模式,当然还有其他常用的设计模式未能涉及,留待后续实践和总结