最近在学习《设计模式》,学校里没有开过这门课,也没听老师怎么提起过,并不了解它是作什么用的,想着以后用到了再学。因为马上要找工作了,想着准备准备,否则到时面试官让你写个设计模式,你说没学过,岂不很尴尬。但看过之后,我只能说相见恨晚。
我曾一度怀疑自己不适合编程,因为如果要我实现稍复杂的业务功能,根本不知道该怎么写或者布局哪些类和方法,看别人的代码脑子也会乱掉。现在明白了,很大程度上是因为我对“设计模式”太无知,不知道编程原来都是有“套路”的。不掌握这些套路,在程序员这条路上会走的很艰难。
因为自己本身搞的是Android开发,最终目的是可以将设计模式与Android各方面(开发、系统源码解读)结合起来,不过由于目前经验有限,所以这些工作还得一步步来,遇到的时候就做一个总结。
这篇文章主要介绍常见的几大模式,日后会逐渐更新添加,然后对每一个模式的要点做一个梳理,并根据模式思想,写一个代码示例。这里不会对每一个模式进行详细介绍,因为这些内容看书是最系统、最易理解和掌握的。我的博文可以作为刚入门的读者的一个辅助,重点是理清思路,能够用代码表示出来,有不正确的地方,还望批评指正。
一、单例模式##
1、定义###
确保一个类只有一个实例,而且自行实例化,并提供一个全局访问点。
1)确保只有一个实例:这个是单例模式所要实现的目的,没什么可说的,从它的名字就可以看出。
2)自行实例化:实例化只能通过new来实现,自行实例化就是只能在类内部进行new操作,否则没有别的办法来确保单例。
3)提供一个全局的访问点:因为我们是在类内部进行的实例化,如果其内部再不提供访问点,那这个类就没法使用了呀。
2、代码示例###
单例模式的实现方式有很多种,比如懒汉模式、饿汉模式、DCL模式、静态内部类模式等。每一种方法都能够实现上面我们所定义的内容,那怎么还会有这么多的方法呢?区别就在于,在一些性能、安全等细节上,它们的处理是不同的,有的有考虑性能,有的则完全没有考虑。主要有这么几个细节问题:
1)线程安全:在多线程的情况下,要确保进行实例化的方法只有一个线程在执行;
2)延迟实例化:意思就是说当我们要用到这个类的时候,再进行实例化操作,而不是像静态成员那样,在类加载的时候就进行了实例化操作,如果永远不去使用,会造成内存的浪费。
3)性能开销要小:这个主要是针对synchronized关键字来说的,如果一个方法被声明为synchronized的,它的性能消耗是要大于普通方法的,如果要该方法频繁调用的话,性能消耗会比较大。
所以基于以上原则,才出现了不同的方法来实现单例模式,有的遵守了上面的原则,有的则没有。我下面使用“静态内部类”的方式来举例实现单例模式,因为它比较好的照顾到了上面三个原则。
public class Singleton{
privat Singleton(){}; //单例模式的构造函数必须是private的,使其无法在外部进行实例化
/*提供全局访问点*/
public static Singleton getInstance(){
return SingletonHolder.mInstance;
}
/*静态内部类只有在第一次被加载的时候才会被实例化,
* 这样就实现了延迟实例化,即在使用到的时候才进行实例化,节省内存,
* 而且不用使用synchronized,避免了性能消耗。
* 同时解决了饿汉模式、懒汉模式的问题*/
private static class SingletonHolder{
private static final Singleton mInstance = new Singleton();
}
}
二、工厂方法模式##
1、定义###
定义一个创建对象的接口,但由子类决定要实例化哪一个类。
单从定义来看,这个定义只是陈述了一个事实:由子类决定要实例化哪一个类。但并没有体现出这个模式想达成的目的是什么,下面,我们先来用代码示例,然后进行一个总结。
2、代码示例###
工厂模式包含如下四个角色:
- Product:抽象产品
- ConcreateProduct:具体产品
- Factory:抽象工厂
- ConcreateFactory:具体工厂

//抽象产品类
public abstract class Product{
public abstract void method();
}
//具体产品类A
public class ConcreateProductA extends Product{
@Override
public void method() {
System.out.print("我是具体产品A");
}
}
//抽象工厂类
public abstract class Factory{
public abstract Product createProduce();
}
//具体工厂类A
public class ConcreateProductA extends Factory{
@Override
public Product createProduce() {
return new ConcreateProductA();
}
}
//客户类(测试)
public class Client{
public static void main(String[] args){
Factory factory = new ConcreateFactoryA();
Product p = factory.createProduct();
p.method();
}
}
3、总结###
结合上面的UML图和代码可以看出,使用工厂方法模式:
1)用户只需要关心所需产品对应的工厂,无需关心创建细节,所有的细节都封装在了具体工厂类内部,甚至无需知道具体产品类的类名,实现了将客户从具体产品类中解耦。
2)在系统中加入新产品时,无须修改抽象工厂和抽象产品提供的接口,无须修改客户端,也无须修改其他的具体工厂和具体产品,而只要添加一个具体工厂和具体产品就可以了。这样就符合了开闭原则,即:对修改关闭,对扩展开放。
三、观察者模式##
1、定义###
定义了对象之间一对多的依赖,每当一个对象改变状态时,则所有依赖于它的对象都会得到通知并被自动更新。
一对多:这个“一”就是被观察者,“多”就是指有多个观察者。当这一个被观察者的状态改变时,则所有的观察者都会得到通知并做出响应。
2、代码示例###
观察者模式包含如下四类角色:
- Subject:抽象主题,也就是被观察(Observable)的角色。
- ConcreateSunject:具体主题,也就是具体被观察者
- Observer:抽象观察者,是观察者的抽象类
- ConcreateObserver:具体观察者

public interface Subject{
void registerObserver(Observer observer);
void removeObserver(Observer observer);
void removeAllObservers();
void notifyObserver(Observer observer, Object data);
void notifyAllObservers( Object data);
}
public class ConcreateSubject implements Subject{
List<Observer> mList = new ArrayList<Observer>();//观察者集合,用于管理存放所有的观察者
@Override
public void registerObserver(Observer observer) {
if(!mList.contains(observer)){
mList.add(observer);
}
}
@Override
public void removeObserver(Observer observer) {
mList.remove(observer);
}
@Override
public void removeAllObservers() {
mList.clear();
}
@Override
public void notifyObserver(Observer observer, Object data) {
if(observer != null){
observer.update(this,data);
}
}
@Override
public void notifyAllObservers(Object data) {
for(Observer observer : mList){
observer.update(this,data);
}
}
}
public interface Observer{
void update(Subject subject,Object data);
}
//定义其中的一个观察者A,还可以定义其它的任意多个观察者
public class ObserverA implements Observer{
@Override
public void update(Subject subject, Object data) {
doSomething();
}
}
3、总结###
1)观察者模式的一个重要作用就是解耦,将观察者和被观察者解耦,使得它们之间依赖性更小,甚至做到毫无依赖;
2)观察者模式可以实现表示层和数据逻辑层的分离,并提供了稳定的消息更新传递机制,使得可以有各种各样不同的表示层作为具体的观察者角色。
3)很明显,观察者模式符合“开闭原则”。
四、装饰者模式##
1、定义###
动态的给一个对象添加一些额外的职责。就增加功能来说,装饰者模式相比生成子类更为灵活。
从定义可以看出,装饰者模式的目的就是:在不需要创造更多子类的情况下,将对象的功能加以扩展。
2、代码示例###
装饰者模式包含如下四类角色:
- Component:抽象组件。
- ConcreteComponent:组件具体实现类。
- Decorator:抽象装饰者,其内部一定要有一个指向组件对象的引用。
- ConcreteDecorator:装饰者具体实现类。

//定义抽象组件类
public abstract class Component{
public abstract void operation();
}
//定义具体实现类
public class ConcreteComponent extends Component{
@Override
public void operation() {
doSomething();
}
}
//定义抽象装饰者类
public abstract class Decorator extends Component{
private Component component;//持有一个Component对象的引用
//必要的构造方法,需要一个Component对象
public Decorator(Component component){
this.component = component;
}
@Override
public void operation() {
doSomething();
}
}
//定义装饰者具体实现类A,其它的具体实现类与此类似
public class ConcreteDecoratorA extends Decorator{
public ConcreteDecoratorA(Component component) {
super(component);
}
@Override
public void operation() {
//装饰方法A和B既可在父类方法前调用,也可在之后调用
operationA();
super.operation();
operationB();
}
public void operationA(){
doSomething();
}
public void operationB(){
doSomething();
}
}
//客户类(测试)
public class Client{
public static void main(String[] args){
Component componentn = new ConcreteComponent();
Decorator decorator = new ConcreteDecoratorA(componentn);
decorator.operation();
}
}
3、总结###
1)装饰者模式和继承的目的都是要扩展对象的功能,但是装饰者模式有更高的灵活性;
2)通过使用不同的具体装饰类以及这些装饰类的排列组合,可以创造出很多不同行为的组合,得到功能更为强大的对象。
五、命令模式##
1、定义###
将一个请求封装成对象,从而让用户使用不同的请求、队列或者日志来参数化其它对象。命令模式也支持可撤销的操作。
这个定义我只读懂了第一句话,后面的没太看懂,不过对这个模式大体是如何工作的有了一个简单的了解。为了弄明白这个模式,下面通过一个例子来解释命令模式的工作流程,当一提到这个模式的时候,脑海中就会立刻浮现出如下的画面,当一想到这个方面,你就会立刻回忆起命令模式是怎么回事。(这个例子来自《Head First设计模式》,有书的同学直接看书好了)没书的同学,请听我下面的简单介绍,故事如下:
故事发生在一家餐厅……
角色介绍:
- 顾客
- 服务员
- 订单
- 厨师
有以上四个角色,一家餐厅就可以很好的运营了,我们想象一下有一位顾客来餐厅吃饭的场景:顾客来吃饭,他知道自己想吃的是什么,并将其写在了订单上——>之后,服务员拿走了订单,并通知厨师准备饭菜——>厨师拿到订单,开始做菜——>菜做好了………
好了,故事讲完了,这就是命令模式的大体的工作流程。不过你也许会疑惑,故事里好像没有怎么体现出“命令”这回事?一开始我也稍有疑惑,心想没看出来谁发布了命令啊,难道是顾客命令服务员?还是服务员命令厨师去做饭?
后来懂了,“命令”在这里是一个名词,是指一系列需要做的动作,而不是一个动词,不是谁命令谁的意思。这就好懂了,那回到上面的故事,订单就是命令。我们下面进入到这个模式:
2、代码示例###
命令模式包含如下四个角色:
- Command:抽象命令类
- ConcreteCommand:具体命令类(订单)
- Invoker:调用者(服务员)
- Receiver:接收者(厨师)
- Client:客户类(顾客)

public interface Command{
void execute(); //执行具体操作的命令
}
public class ConcreteCommand implements Command{
private Receiver receiver; //持有一个对接收者对象的引用
public ConcreteCommand(Receiver receiver){
this.receiver = receiver;
}
@Override
public void execute() {
receiver.action(); //调用接收者的方法来执行具体逻辑
}
}
public class Receiver{
//执行具体命令的方法
public void action(){
doSomething(); //执行具体操作
}
}
public class Invoker{
private Command command; //持有一个对相应命令对象的引用
public Invoker(Command command){
this.command = command;
}
public void action(){
command.execute();
}
}
public class Client{
public static void main(String[] args){
Receiver receiver = new Receiver();
Command command = new ConcreteCommand(receiver);
Invoker invoker = new Invoker(command);
invoker.action();
}
}
3、总结###
命令模式将发出请求的对象和执行请求的对象解耦,降低了系统的耦合度。而且,新的命令也可以很容易的加入到系统中。
六、适配器模式#
1、定义##
适配器模式把一个类的接口变成客户端所期待的另一种接口,从而使原本因接口不匹配而无法在一起工作的两个类能够在一起工作。
说到底,适配器是将两个不兼容的类融合在一起,它有点像粘合剂,将不同的东西通过一种转换使得它们能够协作起来。
2、代码示例##
适配器模式有两种:类适配器和对象适配器。因为对象适配器更为灵活和实用,所以这里只讲对象适配器。适配器模式包含如下三个角色:
- Target:目标角色
- Adaptee:需要适配的接口
- Adapter:适配器

这里有一点需要注意,也是对象适配器与类适配的区别:对象适配器不是使用继承关系连接到Adaptee类,而是使用组合的形式。
下面用电源接口来举例写代码,具体我就不介绍了,大家一看代码就明白,很简单:
//Target角色
public interface FiveVolt{
public int getVolt5();
}
//Adaptee角色
public class Volt220{
public int getVolt220(){
return 220;
}
}
//对象适配器
public class VoltAdapter implements FiveVolt{
Volt220 mVolt220;
public VoltAdapter(Volt220 adaptee){
mVolt220 = adaptee;
}
public int getVolt220(){
return mVolt220.getVolt220();
}
@Override
public int getVolt5() {
return 5;
}
}
//测试
public class Test{
public static void main(String[] args){
VoltAdapter adapter = new VoltAdapter(new Volt220());
System.out.print("输出电压:"+adapter.getVolt5());
}
}
3、总结##
1)Adapter通常应用于进行不兼容的类型转换的场景;
2)还有一种就是输入有无数种情况,但是输出类型是统一的。如果你是搞Android开发,RecyclerView或者ListView中就是这种使用场景。
网友评论