GoF出版的《设计模式:可复用面向对象软件的基础》一书中,共记录了23种设计模式,这些设计模式本质是对面向对象设计的封装、继承、多态的理解。app开发过程中用的比较多的有8种设计模式。
单例模式
单例模式是我们在开发种经常使用到的一种设计模式。单例模式创建的类在当前进程种只有一个实例,并有一个访问它的全局入口。
1.单例模式的优点
- 内存中只有一个对象实例,节省了内存空间。
- 避免了频繁创建实例带来的性能消耗。
- 提供了一种全局访问入口,例如读取配置信息。
2.单例模式的缺点
- 一般静态类不提供接口实现、抽象方法等功能,扩展能力差,修改的话只能在这个单例类里面进行。
- 由于静态模式使用了static全局变量,所以涉及生命周期的引用,这样很容易引起内存泄露,例如传入了一个Activity类,这个时候我们需要 传入的是跟static生命周期一样长的Application Context,否则就不要使用单例模式,例如Dialog对话就不要使用单例模式。
3.单例模式适用场景
- 对象需要保存一些状态信息。
- 避免多重读写操作。例如多个实例读取了同一资源文件,后续涉及对这个资源文件写入同步的操作。
单例模式实现
//单例模式的实现很多中,这里展示一种最常用的实现。代码如下:
public class SingletonDemo {
private static volatile SingletonDemo sInstance = null;
private SingletonDemo(){}
public static SingletonDemo getInstance(){
if(sInstance == null){
synchronized (SingletonDemo.class){
if(sInstance == null){
sInstance = new SingletonDemo();
}
}
}
return sInstance;
}
public void printSomething(){
System.out.println("this is a singleton");
}
}
这种写法的好处有以下几点。
- 构造函数private,不能直接new对象,保证通过 getInstance方法来创建。
- 由于不能直接new对象,所以getInstance方法必须是一个static方法;而静态方法不能访问非静态成员变量,所以这个实例变量也必须是static的。
- 双重检查锁,使用volatile关键字,重排序被禁止,所有的写操作都将发生在读操作之前。适合于一写多读的场景(即一个线程进行写操作,多个线程进行读操作)
//使用静态内部类的方式实现
public class Singleton{
private Singleton(){}
public static Singleton getInstance(){
return SingletonHolder.sInstance;
}
private static class SingletonHolder{//该类之所以是static是因为sInstance是静态的,如果不设为静态会报错
private static final Singleton sInstance=new Singleton();//重点在于确定是哪个类的单例
}
public void printSomething(){
System.out.println("this is a singleton");
}
}
静态类
静态类大家应该很熟悉,用static修饰的方法或者变量,可以直接调用,方便快捷。
public class StaticDemo{
public static void printSomething(){
System.out.println("this is a singleton");
}
}
这是一个最简单的静态类,提供一个静态方法printSomething()。
1.静态类的优点
- 静态类的方法直接调用即可,无须new一个实例对象。
- 静态类的性能较好,因为静态类的方法是在编译期间就绑定了的。
2.静态类的缺点
- 静态类方法不能被复写,没有扩展性。
- 静态类做不到懒加载。
单例类和静态类的选择
单例表现的是类,静态类表现的是方法。
- 如果需要类的扩展能力,例如Override,选择单例模式。
- 如果类比较重,需要考虑懒加载,选择单例模式。
- 如果有状态信息维护需求,选择单例模式。
- 如果有资源文件访问需求,选择单例模式。
- 如果需要将一些方法集中在一起,选择静态类。
工厂模式
所谓的工厂,通俗来讲就是用来生产产品的地方。从代码的角度来说,产品就是一个个具体的类的实例对象,工厂也是一个实例对象,用来生产这些实例产品。工厂模式就是解决实例化对象的问题。
简单工厂
1.优点
工厂类承担创建所有产品的职责,只要有想要创建的产品实例,都可以在工厂类里面实现,工厂类号称“万能类”。
2.缺点
- 只要新增一个产品,就会对工厂类进行修改,违反了设计模式中的“开闭原则”,即对修改关闭(新增产品需要修改工厂类),对扩展开放(没有扩展)。
- 工厂类会随着产品种类的增多变得庞大,而且不易于管理和维护。
package com.brett.myapplication;
public class Main {
private interface ICar{
void move();
}
private static class Benz implements ICar{
@Override
public void move() {
}
}
private static class BMW implements ICar{
@Override
public void move() {
}
}
private static class SimpleFactory{
public static ICar getCar(int carType){
switch (carType){
case 0:
return new Benz();
case 1:
return new BMW();
}
return null;
}
}
public static void main(String[] args) {
ICar car = SimpleFactory.getCar(0);
car.move();
}
}
工厂方法
1.优点
- 弱化一个工厂类通用的概念,将生产产品的职责交给各自的产品工厂去完成,也就是每一个产品都有一个工厂类,负责完成本身产品的生产。
- 符合“开闭原则”,对修改关闭(无须修改工厂类),对扩展开放(新增产品对应的工厂类)。
2.缺点
- 工厂方法实现了多个工厂类,相对简单工厂来说,使用起来更复杂。
- 缺少形成产品族的功能,这个后续可在抽象工厂模式中解决。
package com.brett.myapplication;
public class Main {
private interface ICar{
void move();
}
private interface IFactory{
ICar getCar();
}
private static class Benz implements ICar{
@Override
public void move() {
}
}
private static class BMW implements ICar{
@Override
public void move() {
}
}
private class BenzFactory implements IFactory{
@Override
public ICar getCar() {
return new Benz();
}
}
private class BMWFactory implements IFactory{
@Override
public ICar getCar() {
return new BMW();
}
}
public void get(){
IFactory factory = new BenzFactory();
ICar car = factory.getCar();
car.move();
}
}
3.工厂方法的实现:泛型
package com.brett.myapplication;
public class CarFactory {
private interface ICar{
void move();
}
private interface IFactory{
ICar getCar();
}
private static class Benz implements ICar {
@Override
public void move() {
}
}
private static class BMW implements ICar {
@Override
public void move() {
}
}
public static ICar createCar(Class<? extends ICar>c){//所有的产品必须实现ICar接口
try{
return c.newInstance();
}catch (Exception e){
e.printStackTrace();
}
return null;
}
ICar bnw = CarFactory.createCar(BMW.class);
}
4.工厂方法的实现:Enum
enum EnumCarFactory{
Benz{
@Override
public ICar create(){
return new Benz();
}
},
BMW{
@Override
public ICar create(){
return new BMW();
}
};
public abstract ICar create();
}
private void create(){
try {
ICar ACar = EnumCarFactory.valueOf("Benz").create();
ACar.move();
}catch (Exception e){
e.printStackTrace();
}
}
抽象工厂
1.简介
抽象工厂由“产品族”的概念拓展而来。
一个产品不止一个功能,例如我们为用户制定一套出行方案,这个方案里面配备的由车辆,穿的衣服等,这些功能合在一起就成为“人群”这个产品的功能。如果只配备了车辆,那就跟工厂方法模式一样,只有一个功能,这是极端的情况。
所谓的抽象工厂指的是工厂不止生产某一具体的产品,而是能扩展到生产一系列的产品。
package com.brett.myapplication;
public class CarFactory {
private interface ICar{
void move();
}
private interface IClothes{
void wear();
}
private class Gucci implements IClothes{
@Override
public void wear() {
}
}
private class Prada implements IClothes{
@Override
public void wear() {
}
}
private interface IAbsFactory{
ICar getCar();
IClothes getClothes();
}
private class ZhangSan implements IAbsFactory{
@Override
public ICar getCar() {
return new BMW();
}
@Override
public IClothes getClothes() {
return new Gucci();
}
}
private class LiSi implements IAbsFactory{
@Override
public ICar getCar() {
return new Benz();
}
@Override
public IClothes getClothes() {
return new Prada();
}
}
private class Benz implements ICar {
@Override
public void move() {
}
}
private class BMW implements ICar {
@Override
public void move() {
}
}
void get(){
IAbsFactory absFactory = new ZhangSan();
ICar car = absFactory.getCar();
car.move();
IClothes clothes = absFactory.getClothes();
clothes.wear();
}
}
Builder模式
为什么要用Builder模式
Builder模式主要用于解决初始化类时(也就是new一个类的实例出来),类的构造函数种类过多且不易管理的问题。
Builder模式的实现
我们的想法是Student类的构造函数不要那么多,但是又要满足初始化Student类变量的需求。可以考虑设计一个内部类,这个内部类的参数跟Student类的参数一样,而Student类的构造函数的参数,我们就设定为这个内部类。因此,只需要将这个内部类的变量初始化即可。
内部类变量设定的时候,我们采用链式结构,这样可以通过setXX().setXX()形式一直写下去。
public class Student{
private String name;
private int age;
private boolean sex;
public Student(){}
public static StudentBuilder newInstance(){
return new StudentBuilder();
}
public static class StudentBuilder{
private String name;
private int age;
private boolean sex;
public StudentBuilder setName(String name){
this.name = name;
return this;
}
public StudentBuilder setAge(int age){
this.age = age;
return this;
}
public StudentBuilder setSex(boolean sex){
this.sex = sex;
return this;
}
public Student build(){
return new Student();
}
}
@Override
public String toString() {
return "Student{" +
"name='" + name + '\'' +
", age=" + age +
", sex=" + sex +
'}';
}
}
模板模式
模板模式介绍
模板模式是一种基于代码复用的设计模式。具体实现需要架构师和开发人员之间进行合作。架构师构造好实现的流程和轮廓,开发人员则完成具体的实现过程。
父类抽象模板的作用如下。
- 定义abstract限定符方法并交由子类实现。
- 定义非private方法,延迟至子类实现。
子类实现模板的作用如下。
- 实现父类的abstract方法。
- 可以重写父类非private方法。
public abstract class Car{
void startUp(){
System.out.println("启动!");
}
abstract void move();//强制要求实现
void stop(){
System.out.println("熄火!");
}
public final void operation(){//定义为final,防止被重写
startUp();
move();
stop();
}
public class BMW extends Car{
@Override
void move() {
System.out.println("BWM move!!!");
}
}
public class Benz extends Car{
@Override
void move() {
System.out.println("Benz move!!!");
}
}
观察者模式
观察者模式,包括观察者和被观察者。观察者们将自己的需求告知被观察者,被观察者负责通知到观察者。
Java自带的观察者
package com.brett.myapplication;
import java.util.Observable;
import java.util.Observer;
public class Main {
//被观察者
public static class Server extends Observable{
private int time;
public Server(int time){
this.time = time;
}
public void setTime(int time){
if(this.time == time){
setChanged();//一定要标注,表明数据变更,需要通知观察者
notifyObservers(time);
}
}
}
public static class Client implements Observer{
private String name;
public Client(String name){
this.name = name;
}
@Override
public void update(Observable observable, Object o) {
if(observable instanceof Server){
// TODO
System.out.println("变化了");
}
}
}
public static void main(String[] args) {
Server server = new Server(2019);
Client client1 = new Client("张三");
Client client2 = new Client("李四");
server.addObserver(client1);
server.addObserver(client2);
server.setTime(2020);
}
}
注意,不需要观察者需要移除观察者
if(server!=null){
server.deleteObservers();
}
自己实现观察者模式
具体可以参考这篇博客
常用设计者模式—观察者模式
适配器模式
在什么场合下需要用到适配器模式呢?
现有系统扩展,需要接入第三方系统,也就是接入第三方API:如Class有3个字段A、B、C,需要再添加外部类OuterClass的两个字段,而且还要再不影响当前Class的情况下添加。
适配器模式提供的解决方案如下。
- 因为要兼容原有类,所以原有类需要面向接口编程,也就是要有原有类的接口实现。
- 适配器的目的是兼容原有类,所以适配器也必须实现原有类的接口。
- 适配器内部实现具体的适配方案。
public interface IAmericanCharger{
void charge4American();
}
public class AmericanCharger implements IAmericanCharger{
@Override
public void charge4American() {
System.out.println("do American charge!");
}
}
public interface IChineseCharger{
void charge4Chinese();
}
public class ChineseCharger implements IChineseCharger{
@Override
public void charge4Chinese() {
System.out.println("do American charge!");
}
}
public class AmericanDevice{
private IAmericanCharger iAmericanCharger;
public AmericanDevice(IAmericanCharger iAmericanCharger){
this.iAmericanCharger = iAmericanCharger
}
public void work(){
iAmericanCharger.charge4American();
}
}
public class Adapter implements IAmericanCharger{
private IChineseCharger iChineseCharger;
public Adapter(IChineseCharger iChineseCharger){
this.iChineseCharger = iChineseCharger
}
@Override
public void charge4American() {
iChineseCharger.charge4Chinese();
}
}
public void get(){
IChineseCharger chineseCharger = new ChineseCharger();
Adapter adapter = new Adapter(chineseCharger);
AmericanDevice device = new AmericanDevice(adapter);
device.work();
}
策略模式
Android的每一种模块都有很多种解决方案,例如网络模块有okhttp、vollery等;图片模块有Glide、Picaso等。平时开发的时候可能就会选定一种模块,例如图片就用Glide,然后在项目的代码里面直接调用Glide的接口来完成图片的处理。
如果我们要更换另一种实现方式的话,通常的做法是:将项目种所有用到的原先模块的API,统一换成新引入的模块的API。这样不仅工程量巨大,还容易造成新的问题;而且新的模块的API也需要重新了解和熟悉。
策略模式的实现
根据设计模式的“开闭原则”,我们应该尽量做到对修改关闭。如果使用上述提到的解决方案,那么相当于业务跟模块之间强耦合了。
那么怎么解决这种“前耦合”呢?答案是面向接口编程。我们在使用模块功能的时候,尽量不要直接使用模块提供的API接口,而是使用我们定义的接口提供的方法。
具体的解决方案如下。
- 定义一个接口,这个接口的方法就是我们项目里面所调用的。
- 所有引用的模块都要实现这个接口,虽然模块本身有自己的API,但是我们现在不是直接使用模块的API,而是使用我们定义的接口。所以这个模块必须要实现我们定义的接口。
- 提供一个使用类,通常是单例模式。这个使用类就是我们项目里面所直接调用的,这个使用类可以实现或不实现我们定义的接口。
- 在使用类种指定所引用的第三方模块。
这种解决方案的好处是,无论怎么替换第三方模块,项目使用到这个模块功能的地方都不需要改动,只需要在配置里面设定好使用的第三方模块即可。
public interface ILogProcessor{
void v(String log);
void d(String log);
void i(String log);
void e(String log);
}
public class DefaultLogProcessor implements ILogProcessor{
@Override
public void v(String log) {
Log.v("DefaultLogProcessor",log);
}
@Override
public void d(String log) {
Log.d("DefaultLogProcessor",log);
}
@Override
public void i(String log) {
Log.i("DefaultLogProcessor",log);
}
@Override
public void e(String log) {
Log.e("DefaultLogProcessor",log);
}
}
//提供一个使用类
public class LogLoader implements ILogProcessor{//可以不实现ILogProcessor,自定义对外的方法名
private static volatile LogLoader sInstance = null;
private static ILogProcessor sILogProcessor;
private LogLoader(){}
public static LogLoader getInstance(){
if(sInstance == null){
synchronized (LogLoader.class){
if(sInstance == null){
sInstance = new LogLoader();
}
}
}
return sInstance;
}
//通过选定使用哪一个日志功能类
public static ILogProcessor load(ILogProcessor logProcessor){
return sILogProcessor = logProcessor;
}
@Override
public void v(String log) {
sILogProcessor.v(log);
}
@Override
public void d(String log) {
sILogProcessor.d(log);
}
@Override
public void i(String log) {
sILogProcessor.i(log);
}
@Override
public void e(String log) {
sILogProcessor.e(log);
}
}
代理模式
代理是一个中间者的角色,它屏蔽了访问方和委托方之间的直接接触。也就是说访问方不能直接调用委托方的这个对象,而是必须实例化一个跟委托方有同样接口的代理方,通过这个代理方完成对委托方的调用。
什么时候需要用到代理模式?
- 访问方不想和委托方有直接接触,或者直接接触有困难。
- 访问方对委托方的访问需要增加额外的处理,例如访问前和访问后都做一些处理。
代理模式有两类:静态代理和动态代理,具体实现方式可参考下面这篇博客。
设计模式之之代理模式
代理模式应用:简单工厂
class ProxyFactory<T>{
private T client;//目标对象
private IBefore before;
private IAfter after;
public void setClient(T client){
this.client = client;
}
public void setBefore(IBefore before){
this.before = before;
}
public void setAfter(IAfter after){
this.after = after;
}
public <T> T createProxy(){
ClassLoader loader = client.getClass().getClassLoader();
Class[] interfaces = client.getClass().getInterfaces();
InvocationHandler h = new InvocationHandler() {
@Override
public Object invoke(Object o, Method method, Object[] objects) throws Throwable {
if("getName".equals(method.getName())){
//可根据name值过滤方法
}
if(before != null){
before.doBefore();
}
Object result = method.invoke(client,objects);//执行目标对象的目标方法
if (after != null){
after.doAfter();
}
return result;
}
};
return (T) Proxy.newProxyInstance(loader,interfaces,h);
}
//调用
void get(){
ProxyFactory factory = new ProxyFactory();
factory.setBefore(new IBefore() {
@Override
public void doBefore() {
System.out.println("doBefore");
}
});
factory.setClient(new Benz());
factory.setAfter(new IAfter() {
@Override
public void doAfter() {
System.out.println("doAfter");
}
});
ICar car = (ICar)factory.createProxy();
car.move();
}
}
动态代理的应用:AOP(面向切面编程)
AOP的实现方式之一是动态代理。简单来说:AOP能够动态地将代码切入指定地位置,在指定位置上实现编程,从而达到动态改变原有代码地目的。上面的IBefore和IAfter接口实际上就是简单地实现了AOP,例如在invoke具体方法之前和之后插入一些操作。