Java开发工程师进阶篇-必掌握的常用设计模式
设计模式简介
- 设计模式(Design pattern)代表了最佳的实践,通常被有经验的面向对象的软件开发人员所采用。设计模式是软件开发人员在软件开发过程中面临的一般问题的解决方案。这些解决方案是众多软件开发人员经过相当长的一段时间的试验和错误总结出来的。
- 设计模式分为23 种经典的模式,根据用途我们又可以分为三大类。分别是创建型模式、结构型模式和行为型模式。
-
创建型模式:
- 创建型模式提供了一种在创建对象的同时隐藏创建逻辑的方式,而不是使用 new 运算符直接实例化对象。这使得程序在判断针对某个给定实例需要创建哪些对象时更加灵活。
-
结构型模式:
- 结构型模式旨在通过改变代码结构来达到解耦的目的,使得我们的代码容易维护和扩展。
-
行为型模式:
- 行为型模式关注的是各个类之间的相互作用,将职责划分清楚,使得我们的代码更加地清晰。
设计模式的六大原则
1. 开闭原则(Open Close Principle)
开闭原则的意思是:对扩展开放,对修改关闭。在程序需要进行拓展的时候,不能去修改原有的代码,实现一个热插拔的效果。简言之,是为了使程序的扩展性好,易于维护和升级。想要达到这样的效果,我们需要使用接口和抽象类,后面的具体设计中我们会提到这点。
2. 里氏代换原则(Liskov Substitution Principle)
里氏代换原则是面向对象设计的基本原则之一。 里氏代换原则中说,任何基类可以出现的地方,子类一定可以出现。LSP 是继承复用的基石,只有当派生类可以替换掉基类,且软件单位的功能不受到影响时,基类才能真正被复用,而派生类也能够在基类的基础上增加新的行为。里氏代换原则是对开闭原则的补充。实现开闭原则的关键步骤就是抽象化,而基类与子类的继承关系就是抽象化的具体实现,所以里氏代换原则是对实现抽象化的具体步骤的规范。
3. 依赖倒转原则(Dependence Inversion Principle)
这个原则是开闭原则的基础,具体内容:针对接口编程,依赖于抽象而不依赖于具体。
4. 接口隔离原则(Interface Segregation Principle)
这个原则的意思是:使用多个隔离的接口,比使用单个接口要好。它还有另外一个意思是:降低类之间的耦合度。由此可见,其实设计模式就是从大型软件架构出发、便于升级和维护的软件设计思想,它强调降低依赖,降低耦合。
5. 迪米特法则,又称最少知道原则(Demeter Principle)
最少知道原则是指:一个实体应当尽量少地与其他实体之间发生相互作用,使得系统功能模块相对独立。
6. 合成复用原则(Composite Reuse Principle)
合成复用原则是指:尽量使用合成 / 聚合的方式,而不是使用继承。
常用需掌握的设计模式
1. 工厂模式(Factory Pattern)
基本概念
工厂模式(Factory Pattern)最常用的设计模式之一。这种类型的设计模式属于创建型模式,它提供了一种创建对象的最佳方式。
在工厂模式中,我们在创建对象时不会对客户端暴露创建逻辑,并且是通过使用一个共同的接口来指向新创建的对象。
意图:定义一个创建对象的接口,让其子类自己决定实例化哪一个工厂类,工厂模式使其创建过程延迟到子类进行。
主要解决:主要解决接口选择的问题。
何时使用:我们明确地计划不同条件下创建不同实例时。
如何解决:让其子类实现工厂接口,返回的也是一个抽象的产品。
代码示例
public interface Shape {
void draw();
}
public class Circle implements Shape {
@Override
public void draw() {
System.out.println("画个圆形");
}
}
public class CircleFactory implements Shape {
@Override
public void draw() {
System.out.println("使用简单工厂模式创建圆");
}
}
public static void main(String[] args) {
Shape shape = new CircleFactory();
shape.draw();
}
2. 单例模式(Singleton Pattern)
基本概念
单例模式(Singleton Pattern)是 Java 中最简单的设计模式之一。这种类型的设计模式属于创建型模式,它提供了一种创建对象的最佳方式。
这种模式涉及到一个单一的类,该类负责创建自己的对象,同时确保只有单个对象被创建。这个类提供了一种访问其唯一的对象的方式,可以直接访问,不需要实例化该类的对象。
单例模式的特点:
- 单例类只能有一个实例。
- 单例类必须自己创建自己的唯一实例。
- 单例类必须给所有其他对象提供这一实例。
意图:保证一个类仅有一个实例,并提供一个访问它的全局访问点。
主要解决:一个全局使用的类频繁地创建与销毁。
何时使用:当您想控制实例数目,节省系统资源的时候。
如何解决:判断系统是否已经有这个单例,如果有则返回,如果没有则创建。
代码示例
// 必须掌握的单例模式的写法
/**
* 懒汉非线程安全
*/
public static class LazyNosafe{
private static LazyNosafe lazyNosafe;
public static LazyNosafe getLazyNosafe(){
lazyNosafe = new LazyNosafe();
return lazyNosafe;
}
}
/**
* 懒汉线程安全
*/
public static class LazySafe{
private static LazySafe lazySafe;
synchronized public static LazySafe getLazyNosafe(){
lazySafe = new LazySafe();
return lazySafe;
}
}
/**
* 饿汉模式
*/
public static class HungerSingle{
private static HungerSingle hungerSingle = new HungerSingle();
public static HungerSingle getHungerSingle(){
return hungerSingle;
}
}
/**
* 饿汉模式变种
*/
public static class HungerSingleOther{
private static HungerSingleOther hungerSingleOther;
static {
hungerSingleOther = new HungerSingleOther();
}
public static HungerSingleOther getHungerSingleOther(){
return hungerSingleOther;
}
}
/**
* DCL 双重检查
*/
public static class DclSingleton{
private static DclSingleton dclSingleton;
public static DclSingleton getDclSingleton(){
if(dclSingleton != null){
synchronized (dclSingleton){
if(dclSingleton != null){
dclSingleton = new DclSingleton();
}
}
}
return dclSingleton;
}
}
/**
* DCL 双重检查,优化版
*/
public static class DclSingletonOther{
// 通过 volatile 禁止指令重排,保证按顺序加载
private static volatile DclSingleton dclSingleton;
public static DclSingleton getDclSingleton(){
if(dclSingleton != null){
synchronized (dclSingleton){
if(dclSingleton != null){
dclSingleton = new DclSingleton();
}
}
}
return dclSingleton;
}
}
/**
* 静态内部类
* 利用了 classloader 机制来保证初始化 instance 时只有一个线程
*/
public static class StaticClassSingleton{
private static class StaticSingleton{
public static final StaticSingleton staticSingleton = new StaticSingleton();
}
public static StaticSingleton getStaticSingleton(){
return StaticSingleton.staticSingleton;
}
}
/**
* 枚举的方式
*/
public enum EnumSingleton{
INSTANCE;
}
3. 代理模式(Proxy Pattern)
基本概念
代理模式(Proxy Pattern)中,一个类代表另一个类的功能。这种类型的设计模式属于结构型模式。
在代理模式中,我们创建具有现有对象的对象,以便向外界提供功能接口。
意图:为其他对象提供一种代理以控制对这个对象的访问。
主要解决:在直接访问对象时带来的问题,比如说:要访问的对象在远程的机器上。在面向对象系统中,有些对象由于某些原因(比如对象创建开销很大,或者某些操作需要安全控制,或者需要进程外的访问),直接访问会给使用者或者系统结构带来很多麻烦,我们可以在访问此对象时加上一个对此对象的访问层。
何时使用:想在访问一个类时做一些控制。
如何解决:增加中间层。
代码示例
public interface Cat {
void sayMiao();
void run();
}
public class BlackCat implements Cat {
@Override
public void sayMiao() {
System.out.println("Black miao");
}
@Override
public void run() {
System.out.println("Black run");
}
}
public class PoxyCat implements Cat {
private BlackCat blackCat = new BlackCat();
@Override
public void sayMiao() {
// Before todo
blackCat.sayMiao();
// After todo
}
@Override
public void run() {
// Before todo
blackCat.run();
// After todo
}
}
4. 适配器模式(Adapter Pattern)
基本概念
- 适配器模式(Adapter Pattern)是作为两个不兼容的接口之间的桥梁。这种类型的设计模式属于结构型模式,它结合了两个独立接口的功能。
- 这种模式涉及到一个单一的类,该类负责加入独立的或不兼容的接口功能。举个真实的例子,读卡器是作为内存卡和笔记本之间的适配器。您将内存卡插入读卡器,再将读卡器插入笔记本,这样就可以通过笔记本来读取内存卡。
意图:将一个类的接口转换成客户希望的另外一个接口。适配器模式使得原本由于接口不兼容而不能一起工作的那些类可以一起工作。
主要解决:主要解决在软件系统中,常常要将一些"现存的对象"放到新的环境中,而新环境要求的接口是现对象不能满足的。
何时使用: 1、系统需要使用现有的类,而此类的接口不符合系统的需要。 2、想要建立一个可以重复使用的类,用于与一些彼此之间没有太大关联的一些类,包括一些可能在将来引进的类一起工作,这些源类不一定有一致的接口。 3、通过接口转换,将一个类插入另一个类系中。(比如老虎和飞禽,现在多了一个飞虎,在不增加实体的需求下,增加一个适配器,在里面包容一个虎对象,实现飞的接口。)
如何解决:继承或依赖(推荐)。
代码示例
public interface Cat{
void sayMiao();
void run();
}
public class animalsAdapter implements Cat{
@Override
public void sayMiao() { }
@Override
public void run() { }
}
public class Dog extends animalsAdapter{
@Override
public void run() {
System.out.println("dog run");
}
}
5. 外观模式(Facade Pattern)
基本概念
- 外观模式(Facade Pattern)隐藏系统的复杂性,并向客户端提供了一个客户端可以访问系统的接口。这种类型的设计模式属于结构型模式,它向现有的系统添加一个接口,来隐藏系统的复杂性。
- 这种模式涉及到一个单一的类,该类提供了客户端请求的简化方法和对现有系统类方法的委托调用。
意图:为子系统中的一组接口提供一个一致的界面,外观模式定义了一个高层接口,这个接口使得这一子系统更加容易使用。
主要解决:降低访问复杂系统的内部子系统时的复杂度,简化客户端之间的接口。
何时使用: 1、客户端不需要知道系统内部的复杂联系,整个系统只需提供一个"接待员"即可。 2、定义系统的入口。
如何解决:客户端不与系统耦合,外观类与系统耦合。
代码示例
public interface Shape {
void draw();
}
public class Circle implements Shape {
@Override
public void draw() {
System.out.println("Circle::draw()");
}
}
public class Rectangle implements Shape {
@Override
public void draw() {
System.out.println("Rectangle::draw()");
}
}
public class ShapeMaker {
private Shape circle;
private Shape rectangle;
public ShapeMaker() {
circle = new Circle();
rectangle = new Rectangle();
}
public void drawCircle() {
circle.draw();
}
public void drawRentangle() {
rectangle.draw();
}
}
6. 策略模式(Strategy Pattern)
基本概念
- 在策略模式(Strategy Pattern)中,一个类的行为或其算法可以在运行时更改。这种类型的设计模式属于行为型模式。
- 在策略模式中,我们创建表示各种策略的对象和一个行为随着策略对象改变而改变的 context 对象。策略对象改变 context 对象的执行算法。
意图:定义一系列的算法,把它们一个个封装起来, 并且使它们可相互替换。
主要解决:在有多种算法相似的情况下,使用 if...else 所带来的复杂和难以维护。
何时使用:一个系统有许多许多类,而区分它们的只是他们直接的行为。
如何解决:将这些算法封装成一个一个的类,任意地替换。
代码示例
public interface Strategy{
void draw();
}
public class Circle implements Strategy{
@Override
public void draw() {
}
}
public class Rentangle implements Strategy{
@Override
public void draw() {
}
}
public class StrategyContext{
private Strategy strategy;
public StrategyContext(Strategy strategy){
this.strategy = strategy;
}
public void draw(){
strategy.draw();
}
}
7. 模板方法模式(Template Pattern)
基本概念
- 在模板模式(Template Pattern)中,一个抽象类公开定义了执行它的方法的方式 / 模板。它的子类可以按需要重写方法实现,但调用将以抽象类中定义的方式进行。这种类型的设计模式属于行为型模式。
意图:定义一个操作中的算法的骨架,而将一些步骤延迟到子类中。模板方法使得子类可以不改变一个算法的结构即可重定义该算法的某些特定步骤。
主要解决:一些方法通用,却在每一个子类都重新写了这一方法。
何时使用:有一些通用的方法。
如何解决:将这些通用算法抽象出来。
代码示例
public abstract class AbstractTemplate{
public void execute(){
init();
dosomething();
end();
}
public void init(){
System.out.println("初始化");
}
public void dosomething(){
}
public void end(){
System.out.println("结束执行");
}
}
public class DemoTemplate extends AbstractTemplate{
@Override
public void dosomething() {
System.out.println("自定义方法");
}
}
public static void main(String[] args) {
AbstractTemplate abstractTemplate = new DemoTemplate();
abstractTemplate.execute();
}
8. 观察者模式(Observer Pattern)
基本概念
当对象间存在一对多关系时,则使用观察者模式(Observer Pattern)。比如,当一个对象被修改时,则会自动通知它的依赖对象。观察者模式属于行为型模式。
意图:定义对象间的一种一对多的依赖关系,当一个对象的状态发生改变时,所有依赖于它的对象都得到通知并被自动更新。
主要解决:一个对象状态改变给其他对象通知的问题,而且要考虑到易用和低耦合,保证高度的协作。
何时使用:一个对象(目标对象)的状态发生改变,所有的依赖对象(观察者对象)都将得到通知,进行广播通知。
如何解决:使用面向对象技术,可以将这种依赖关系弱化。
代码示例
public class Subjects {
private List observerList = new ArrayList<>();
private String state;
public void attachOb(Observer observer) {
observerList.add(observer);
}
public void notifyAllObserver() {
for (Observer observer : observerList) {
observer.updata();
}
}
public String getState() {
return state;
}
public void setState(String state) {
this.state = state;
notifyAllObserver();
}
}
public abstract class Observer {
protected Subjects subject;
public abstract void updata();
}
public class DemoOb extends Observer {
public DemoOb(Subjects subject){
this.subject = subject;
subject.attachOb(this);
}
@Override
public void updata() {
System.out.println("DemoOb has updated");
}
}
public static void main(String[] args) {
Subjects subjects = new Subjects();
Observer ob = new DemoOb(subjects);
subjects.setState("123");
}