设计模式

前言

设计模式是一套代码设计经验的总结,使用它是为了可重用代码,让代码更容易被他人理解,保证代码的可靠性、程序的重用性。一共有 23 种设计模式,本文主要记录下常见的几个。

单例设计模式

使用单例模式需要满足以下条件:

  1. 构造器私有化。
  2. 通过一个静态方法或者枚举返回单例类实例。
  3. 确保单例类对象有且只有一个,尤其在多线程情况下。
  4. 确保单例类对象在反序列化时不会重新构建对象(只在实现序列化接口时需要),当实现了序列化接口需要在 readResolve 方法内部返回那个静态变量,因为静态变量不会被序列化,所以直接返回相当于对象没变。

饿汉模式

加载类就进行初始化,可能会用不到导致资源浪费。代码如下:

class A {
private static A a = new A();
private A() {
}
public static A getInstance() {
return a;
}
}

懒汉模式

每次调用 getInstance 都是同步的,效率低下,不推荐用。代码如下:

class B {
private static B b;
private B() {
}
public synchronized static B getInstance() {
if (b == null) {
b = new B();
}
return b;
}
}

双重校验锁

DCL 一般可以使用。代码如下:

class C {
// 1.可见性,一个线程改了另一个线程就能读到最新的值 2.禁止指令重排序
private static volatile C c;
private C() {
}
public static C getInstance() {
if (c == null) {
synchronized(C.class) {
if (c == null) {
// 1.分配内存 2.调用构造器初始化成员变量 3. 将堆中的地址赋值给c,其中如果不设置volatile后两个操作可能会反着调用
c = new C();
}
}
}
return c;
}
}

静态内部类

推荐使用。代码如下:

class D {
private D() {
}
public static D getInstance() {
return DHolder.d;
}
private static class DHolder {
private static final D d = new D();
}
}

枚举单例

推荐使用。代码如下:

enum E {
INSTANCE;
public void doSomeThing() {
System.out.println("单例内部的方法");
}
}

简单工厂设计模式

  1. 对实现了同一接口的类进行实例的创建,拥有三种角色,一个工厂类,一个基类,若干个基类的派生类(或实现类) 。

  2. 使用场景是工厂类负责创建的对象比较少,用户只需要知道传给工厂类的参数就行了,而不需关系具体创建。

  3. 优点: 用户根据参数获得实例,避免了直接实例化类,降低了耦合性。

  4. 缺点: 简单工厂需要知道其所有要生成的类型,当类型过多是不适合使用,并且如果需求变动比如我要添加微博分享,那么就得修改工厂类,违背了开闭原则。

interface ShareChannel {
void share();
}
class QQShareChannel implements ShareChannel {
@Override
public void share() {}
}
class WXShareChannel implements ShareChannel {
@Override
public void share() { }
}
class ShareChannelFactory {
public static final int QQ = 1;
public static final int WX = 2;
public static ShareChannel createShareChannel(int type) {
if (type == QQ) {
return new QQShareChannel();
} else if (type == WX) {
return new WXShareChannel();
} else {
throw new IllegalArgumentException();
}
}
}
class ShareChannelFactoryOptimized {
public static <T extends ShareChannel> ShareChannel createShareChannel(Class<T> clazz) throws Exception {
return clazz.newInstance();
}
}

工厂方法设计模式

  1. 拥有四种角色,抽象工厂类、具体工厂类、基类(或接口)、实现类,把简单工厂的实现修改一下。

  2. 比如微信分享 WXAPIFactory.createWXAPI()、钉钉分享 DDShareApiFactory.createDDShareApi() 都是使用了工厂方法设计模式。

  3. 对比简单工厂模式,当需求改变需要增加微博分享时我们只需要创建一个 WBShareChannel 就行了,没有违背开闭原则。

interface ShareChannel {
void share();
}

interface ShareChannelFactory {
ShareChannel createChannelFactory();
}

class QQShareChannel implements ShareChannel {

@Override
public void share() {}

static class QQShareChannelFactory implements ShareChannelFactory {

@Override
public ShareChannel createChannelFactory() {
return new QQShareChannel();
}
}
}

class WXShareChannel implements ShareChannel {

@Override
public void share() {}

static class WXShareChannelFactory implements ShareChannelFactory {

@Override
public ShareChannel createChannelFactory() {
return new WXShareChannel();
}
}
}

建造者设计模式

  1. 创建一个复杂对象时使用,通过 Builder 类一步一步创建,对比构建器,其参数可选,并且可以无序。
  2. 比如 Android 中的 AlertDialog 其组成非常复杂,比如可以只设置 Title、Message,也可以只设置 Title,有很多种可选的设置。分享功能需要很多的参数,而很多参数都是可选的就可以使用建造者模式。
  3. 其主要的缺点就是产生了多余的类。
class ShareParams {

private String title;
private String content;
private String url;
private String imageUrl;

private ShareParams(Builder builder) {
this.title = builder.title;
this.content = builder.content;
this.url = builder.url;
this.imageUrl = builder.imageUrl;
}

public static class Builder {
private String title;
private String content;
private String url;
private String imageUrl;

public void setTitle(String title) {
this.title = title;
}

public void setContent(String content) {
this.content = content;
}

public void setUrl(String url) {
this.url = url;
}

public void setImageUrl(String imageUrl) {
this.imageUrl = imageUrl;
}

public ShareParams build() {
return new ShareParams(this);
}
}
}

代理设计模式

代理设计模式包括静态代理以及动态代理。

静态代理

  1. 在符合开闭原则的情况下对目标对象进行功能扩展,防止与真实对象接触降低耦合性。
  2. 接口一旦发生改变,代理类也得发生改变。
interface BuyTicket {
void buyTicket();
}

class RealBuyTicket implements BuyTicket {

@Override
public void buyTicket() { }
}

class ProxyBuyTicket implements BuyTicket {

private final BuyTicket buyTicket;

public ProxyBuyTicket() {
this.buyTicket = new RealBuyTicket();
}

@Override
public void buyTicket() {
buyTicket.buyTicket();
}
}

动态代理

相对于静态代理其代理类不用在代码中写死,但是必须要有一个接口才能进行代理,因为 java 不支持多继承。

class DynamicProxy {

public static void main(String[] args) {
BuyTicket buyTicket = (BuyTicket) Proxy.newProxyInstance(DynamicProxy.class.getClassLoader(), new Class[]{ MyInvocationHandler.class }, new MyInvocationHandler(new RealBuyTicket()));
buyTicket.buyTicket();
}

static class MyInvocationHandler implements InvocationHandler {

private final BuyTicket buyTicket;

public MyInvocationHandler(BuyTicket buyTicket) {
this.buyTicket = buyTicket;
}

@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
if (method.getName().equals("buy")) {
method.invoke(buyTicket);
}
return null;
}
}
}

装饰模式

  1. 与装饰模式不同的是,代理模式一般实际类对象都在代理类内部创建(对外隐藏),而装饰模式一般是外界传入的实际类对象。
  2. 优点 可以在不改变被装饰类的情况下加强类的功能,或者类似 Android 里面的 ContextWrap 类,使其子类 Activity、Service 不必包含复杂的实现逻辑(比如 startActivity )。
class ContextImpl implements Context {

@Override
public void startActivity() {
System.out.println("真正启动 Activity ");
}
}

class ContextWrap implements Context {
private Context context;
public ContextWrap(Context context) {
this.context = context;
}

@Override
public void startActivity() {
// do something
context.startActivity();
}
}

外观模式

  1. 一般在封装 SDK 或者 API 时用到,暴露给客户端外观类(该类把子系统的功能进行结合)供客户端使用。
  2. 优点:客户端无需关心具体的子系统的实现,直接与外观类进行交互,降低了耦合性,这样即使具体的子系统的内容发生改变,用户也不会感知到;同时由于客户端只能使用外观类的功能,而不能使用子系统的其他功能所以加强了安全性。
  3. 缺点:不符合开闭原则,如果业务发生变更那么可能要直接修改外观类。
  4. 假设用户有一台手机,手机有打电话系统和拍照系统,手机的外观类是 MobilePhone ,用户只能调用该类中的方法去执行操作,无法调用 CallManager.privateFunction 方法,因此加强了安全性。
class MobilePhone {

private CallManager callManager;
private CameraManager cameraManager;

public static void main(String[] args) {
// 用户拥有一个手机,拿它去拍照,打电话
MobilePhone phone = new MobilePhone();
phone.takePhotos();
phone.call();
}

public MobilePhone() {
callManager = new CallManager();
cameraManager = new CameraManager();
}

public void call() {
callManager.call();
callManager.hangup();
}

public void takePhotos() {
cameraManager.open();
cameraManager.takePhotos();
cameraManager.close();
}
}

/**
* 专门用来打电话的子功能块
*/
class CallManager {
public void call() {
System.out.println("开始通话");
}
public void hangup() {
System.out.println("结束通话");
System.out.println("挂断电话");
}
public void privateFunction() {
System.out.println("内部才能调用的功能");
}
}

class CameraManager {
public void open() {
System.out.println("打开相机");
}
public void takePhotos() {
System.out.println("拍照");
}
public void close() {
System.out.println("关闭相机");
}
}

享元设计模式

  1. 池技术的重要实现技术,可以减少重复对象的创建,降低程序内存的占用,提高性能,一般拥有着以下三个角色
    1. 抽象享元角色,同时定义出对象的外部状态和内部状态的接口。
    2. 具体享元角色,实例的抽象享元角色的业务。
    3. 享元工厂,负责管理对象池和创建享元对象(一般内部用 Map、SpareArray 实现),如下面示例中的 Ticket 抽象类就是抽象享元角色,内部的 from、to 属于内部状态,level 属于外部状态会随着外界状态变化而变化,TrainTicket 属于具体享元角色,TicketFactory 属于享元工厂。
  2. 其实就是缓存的一种,比如 Java 中的 String 一旦常量池有了就不会重新创建、Android 中的 Message 通过一个链表来缓存、Volley 里面的 ByteArrayPool、OkHttp 里面的连接池。
class FlyWeight {
public static abstract class Ticket {
protected String from;
protected String to;
protected SeatLevel level;
protected float price;

public Ticket(String from, String to, SeatLevel level, float price) {
this.from = from;
this.to = to;
this.level = level;
this.price = price;
}
abstract void showTicketInfo();
}

public static void main(String[] args) {
Ticket ticket1 = TicketFactory.getTicket("杭州东", "绍兴北", SeatLevel.SECOND_CLASS_SEAT);
ticket1.showTicketInfo();
Ticket ticket2 = TicketFactory.getTicket("杭州东", "绍兴北", SeatLevel.FIRST_CLASS_SEAT);
ticket2.showTicketInfo();
Ticket ticket3 = TicketFactory.getTicket("杭州东", "绍兴北", SeatLevel.SECOND_CLASS_SEAT);
ticket3.showTicketInfo();
}
public static class TrainTicket extends Ticket {
public TrainTicket(String from, String to, SeatLevel level, float price) {
super(from, to, level, price);
}
public String getFrom() {
return from;
}
public void setFrom(String from) {
this.from = from;
}
public String getTo() {
return to;
}
public void setTo(String to) {
this.to = to;
}
public SeatLevel getLevel() {
return level;
}
public void setLevel(SeatLevel level) {
this.level = level;
}
public float getPrice() {
return price;
}
public void setPrice(float price) {
this.price = price;
}
@Override
public void showTicketInfo() {
System.out.println("从" + from + " 到" + to + "的火车票" + level + "价格为" + price);
}
}

public enum SeatLevel {
BUSINESS_CLASS_SEAT("商务座"),
FIRST_CLASS_SEAT("一等座"),
SECOND_CLASS_SEAT("二等座");

private String desc;

SeatLevel(String desc) {
}
}

public static class TicketFactory {

private static Map<String, Ticket> map = new HashMap<>();

public static Ticket getTicket(String from, String to, SeatLevel level) {
String key = from + to + level;
if (map.containsKey(key)) {
return map.get(key);
} else {
Ticket ticket = new TrainTicket(from, to, level, queryPrice(from, to, level));
map.put(key, ticket);
return ticket;
}
}

private static float queryPrice(String from, String to, SeatLevel level) {
return (float) (Math.random() * 200);
}
}
}

策略设计模式

  1. 针对同一类型的问题拥有多种解决方式,仅仅具体行为有差异,或者有多个派生自同一个类的实现类又需要通过 if-else、switch 判断使用哪个的。
  2. 优点, Context 类只依赖于抽象类 ISort,不依赖与具体实现,Context 只需要知道它操作的是一个 ISort 类就行了 ,遵守了开闭原则,当添加了新的策略时不需要修改 Context 类。
interface ISort {
void sort(int[] numbers);
}

class SortUtils {

public static final int BUBBLE_SORT = 1;
public static final int CHOOSE_SORT = 2;

public static void main(String[] args) {
int[] array = new int[]{4, 2, 1, 5};
sort(array, BUBBLE_SORT);
sort(array, CHOOSE_SORT);
}


public static void sort(int[] array, int type) {
if (type == BUBBLE_SORT) {
new BubbleSort().sort(array);
} else if (type == CHOOSE_SORT) {
new SelectionSort().sort(array);
}
}

}
// 使用了策略模式,当需要添加新的排序算法是不需要改变Context类,遵守了开闭原则
class Context {

private ISort mISort;

public void setISort(ISort iSort) {
mISort = iSort;
}

public static void main(String[] args) {
Context strategy = new Context();
int[] array = new int[]{4, 2, 1, 5};
strategy.setISort(new BubbleSort());
strategy.sort(array);
System.out.println(Arrays.toString(array));
}

public void sort(int[] array) {
if (mISort == null) {
throw new RuntimeException();
}
mISort.sort(array);
}
}

模板方法设计模式

  1. 主要有两种角色, 一个抽象父类,多个派生自该抽象父类的子类。

  2. 某个方法有些步骤是固定的,有些步骤是不固定的,就可以使用该模式,下面以 BaseActivity 为例。

  3. 优点 将不变的行为搬移到超类,去除了子类中的重复代码。

public abstract class BaseActivity extends AppCompatActivity {
@Override
protected void onCreate(@Nullable Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
// 步骤一
initData();
// 步骤二
initView();
// 步骤三
initEvent();
}
/**
* 设置监听事件,需要可重写
*/
protected void initEvent() {}
/**
* 抽象方法,子类必须实现用于设置该Activity的视图
*/
protected abstract void initView();
/**
* 初始化数据,如果需要初始化的话,可以重写该方法
*/
protected void initData() {}
}

观察者设计模式

  1. 拥有4种角色,一个 Observable 接口或类、一个 Observer 接口、一个 Observable 的派生类、多个Observer 接口的实现类。
  2. 优点 观察者与被观察者是抽象耦合易于扩展。
  3. 缺点 由于通知观察者是顺序通知的,如果一个观察者做的事情比较多造成了卡顿,那么就会影响其他后面的观察者。
  4. 下面的代码微信公众号是被观察者,用户是观察者,其中 hfw、cbw 订阅了该微信公众号,而 ym 没有订阅,所以 ym 不会收到该微信公众号的推送(调用 update)。
class ObserverPattern {

public static void main(String[] args) {
User hfw = new User("hfw");
User cbw = new User("cbw");
User ym = new User("ym");
WXOfficialAccounts accounts = new WXOfficialAccounts();
accounts.addObserver(hfw);
accounts.addObserver(cbw);
// 公众号拥有者向公众号中发布了一篇文章
Article article = new Article();
article.setText("Hello, World");
article.setPublicTime(new Date().toLocaleString());
accounts.addArticle(article);
}
static class WXOfficialAccounts extends Observable {
public void addArticle(Article article) {
notifyObservers(article);
}
}
static class User implements Observer {
private String mName;
public User(String name) {
mName = name;
}
@Override
public void update(Observable o, Object arg) {
System.out.println(mName + " 接受到了文章" + arg);
}
}
static class Article {
private String publicTime;
private String text;
public String getPublicTime() {
return publicTime;
}
public void setPublicTime(String publicTime) {
this.publicTime = publicTime;
}
public String getText() {
return text;
}
public void setText(String text) {
this.text = text;
}
@Override
public String toString() {
return publicTime + " " + text;
}
}
}
class Observable {
private List<Observer> list;
public Observable() {
list = new ArrayList<>();
}
public synchronized void addObserver(Observer o) {
if (o == null)
throw new NullPointerException();
if (!list.contains(o)) {
list.add(o);
}
}
public synchronized void deleteObserver(Observer o) {
list.remove(o);
}
public void notifyObservers(Object arg) {
for (int i = list.size()-1; i>=0; i--)
((Observer)list.get(i)).update(this, arg);
}
}
interface Observer {
void update(Observable o, Object arg);
}
0%