设计模式可以分为三种:
1、创建型模式
这些设计模式提供了一种在创建对象的同时隐藏创建逻辑的方式,而不是使用 new 运算符直接实例化对象。这使得程序在判断针对某个给定实例需要创建哪些对象时更加灵活。有5种:
工厂模式(Factory Pattern)
抽象工厂模式(Abstract Factory Pattern)
单例模式(Singleton Pattern)
建造者模式(Builder Pattern)
原型模式(Prototype Pattern)
2、结构型模式
这些设计模式关注类和对象的组合。继承的概念被用来组合接口和定义组合对象获得新功能的方式。有7种:
适配器模式(Adapter Pattern)
桥接模式(Bridge Pattern)
组合模式(Composite Pattern)
装饰器模式(Decorator Pattern)
外观模式(Facade Pattern)
享元模式(Flyweight Pattern)
代理模式(Proxy Pattern)
3、行为型模式
这些设计模式特别关注对象之间的通信。有11种:
责任链模式(Chain of Responsibility Pattern)
命令模式(Command Pattern)
解释器模式(Interpreter Pattern)
迭代器模式(Iterator Pattern)
中介者模式(Mediator Pattern)
备忘录模式(Memento Pattern)
观察者模式(Observer Pattern)
状态模式(State Pattern)
策略模式(Strategy Pattern)
模板模式(Template Pattern)
访问者模式(Visitor Pattern)
一、模板模式(Template Pattern)
模板模式指的是一个抽象类公开定义了执行它的方法的方式/模板。它的子类可以按需要重写方法实现,但调用将以抽象类中定义的方式进行。模板模式事实上才是使用得最多的设计模式,几乎所有OOP的代码都会有意无意碰到。我们以来修房子作为一个例子。
public class DesignPattern {
public static void main(String[] args) {
BuildHouse flat=new BuildFlat();
BuildHouse hotel=new BuildHotel();
flat.build();
System.out.println();
hotel.build();
}
}
abstract class BuildHouse{
protected void start(){//start和end方法的定义在抽象类中,不需要子类覆盖
System.out.println("开始建房子啦");
}
protected abstract void buildpool();//需要子类覆盖接下来的三个方法。这些方法由于用户不需要调用,只会由子类覆盖,所以protected
protected abstract void buildwall();
protected abstract void buildroof();
protected void end(){
System.out.println("建完房子啦");
}
protected boolean isrich() {//钩子方法。钩子方法一般是在抽象类中实现或者在接口中,可以由子类覆盖。他的作用顾名思义,像一个钩子,决定某些方法能否被执行
return true;
}
final public void build(){//build就是所谓模板方法,他决定了方法的具体执行。也就是说方法的具体执行交给抽象类而不是子类,子类只需要覆盖方法的各个步骤的具体实现
//由于具体方法的执行是不能被更改的,所以要用final。由于用户要调用他,所以需要public
this.start();
if(this.isrich())
this.buildpool();
this.buildwall();
this.buildroof();
this.end();
}
}
class BuildFlat extends BuildHouse{
@Override
protected void buildpool() {
System.out.println("建一个公寓游泳池");
}
@Override
protected void buildwall(){
System.out.println("建一个公寓墙壁");
}
@Override
protected void buildroof(){
System.out.println("建一个公寓屋顶");
}
@Override
protected boolean isrich(){//建公寓,那肯定没什么钱,那就别建游泳池了
return false;
}
}
class BuildHotel extends BuildHouse{
@Override
protected void buildpool() {
System.out.println("建一个酒店游泳池");
}
@Override
protected void buildwall(){
System.out.println("建一个酒店墙壁");
}
@Override
protected void buildroof(){
System.out.println("建一个酒店屋顶");
}
}
优点: 1、封装不变部分,扩展可变部分。 2、提取公共代码,便于维护。 3、行为由父类控制,子类实现。
缺点:每一个不同的实现都需要一个子类来实现,导致类的个数增加,使得系统更加庞大。
使用场景:1、有多个子类共有的方法,且逻辑相同。 2、重要的、复杂的方法,可以考虑作为模板方法。
二、单例模式(Singleton Pattern)
单例模式是 Java 中最简单的设计模式之一。这种模式涉及到一个单一的类,该类负责创建自己的对象,同时确保只有单个对象被创建。这个类被称为单例类,他提供了一种访问其唯一的对象的方式,可以直接访问,不需要实例化该类的对象。要注意:1、单例类有且仅有一个实例;2、他必须自己创建自己这个唯一实例;3、必须要给其他所有对象提供这一实例。
对于单例模式,可以分为懒汉初始化和饿汉初始化两种初始化方式。懒汉初始化的意思是在使用到这个单例的时候,这个单例才会被初始化。饿汉初始化的意思是不管有没有使用这个单例,一开始就初始化了。
1、懒汉初始化
class SingleObject{
private static SingleObject instance;//在懒汉模式中不能使用final,因为使用final的话在类加载时候就要赋值,就变成饿汉了
private SingleObject() {}//默认构造方法要是个空方法
public static synchronized SingleObject getInstance() {//要用同步实现线程安全
if(instance==null)
instance=new SingleObject();//用到的时候才初始化
return instance;
}
}
可以看到,上面这个例子对整个getInstance()方法都上了锁,但是getInstance()方法的效率对程序的影响很大,所以我们希望能尽量提高这个方法的效率。其实可以使用一个叫双检锁的机制。。而且volatile有时候效率会高于synchronized。双检锁的意义有两个:第一,只有第一次实例化的时候才需要加锁,如果已经有实例了,就不用加锁了,提高效率;第二,if两个语句判断,防止重复new实例。双检锁其实指的是一个锁和两个检查。volatile其实可有可无。
class SingleObject{
private static volatile SingleObject instance;//第一把锁
private SingleObject() {}
public static SingleObject getInstance() {
if(instance==null) {
synchronized(SingleObject.class) {//第二把锁
if(instance==null)//一定要做两次判断,否则无法保证线程安全
instance=new SingleObject();
}
}
return instance;
}
}
2、饿汉初始化
class SingleObject{
private static final SingleObject instance=new SingleObject();//基于classloader机制
//在类加载时候就实现了初始化,避免了多线程同步,因此线程安全。同时他用了final修饰,因为一开始在类加载时候已经赋值了,所以可以用final
private SingleObject() {}
public static SingleObject getInstance() {
return instance;
}
}
优点:1、在内存里只有一个实例,减少了内存的开销,尤其是频繁的创建和销毁实例; 2、避免对资源的多重占用。
缺点:没有接口,不能继承,与单一职责原则冲突,一个类应该只关心内部逻辑,而不关心外面怎么样来实例化。
使用场景:一些在程序中我们只需要一个实例的情况下,例如产生唯一的序列号、线程池、缓存、日至对象等。
要注意的地方:在getInstance() 方法中需要使用同步锁 synchronized (Singleton.class) 防止多线程同时进入造成 instance 被多次实例化。
三、工厂模式(Factory Pattern)
工厂模式是Java中最常用的设计模式之一。在工厂模式中,我们在创建对象时不会对客户端暴露创建逻辑,并且是通过使用一个共同的接口来指向新创建的对象。
public class DesignPattern {
public static void main(String[] args) {
ShapeFactory shapeFactory=new ShapeFactory();//先new一个工厂方法对象
Shape shape1=shapeFactory.getShape("Circle");//通过工厂方法对象的工厂方法来产生实例,而不是用户自己new
shape1.draw();
Shape shape2=shapeFactory.getShape("rectangle");
shape2.draw();
}
}
class ShapeFactory{
public Shape getShape(String shapeType){//工厂方法,根据传入的String字符串,生产实例
if(shapeType==null)
return null;
if(shapeType.equalsIgnoreCase("Rectangle"))
return new Rectangle();
else if(shapeType.equalsIgnoreCase("Squre"))
return new Squre();
else if(shapeType.equalsIgnoreCase("Circle"))
return new Circle();
else
return null;
}
}
interface Shape{//形状接口
public void draw();
}
class Rectangle implements Shape{//三个接口的实现类
public void draw(){
System.out.println("Draw a rectangle");
}
}
class Squre implements Shape{
public void draw(){
System.out.println("Draw a squre");
}
}
class Circle implements Shape{
public void draw(){
System.out.println("Draw a circle");
}
}
优点: 1、一个调用者想创建一个对象,只要知道其名称就可以了。 2、扩展性高,如果想增加一个产品,只要扩展一个工厂类就可以。 3、屏蔽产品的具体实现,调用者只关心产品的接口。
缺点:每次增加一个产品时,都需要增加一个具体类和对象实现工厂,使得系统中类的个数成倍增加,在一定程度上增加了系统的复杂度,同时也增加了系统具体类的依赖。这并不是什么好事。
使用场景:明确在不同条件下创建不同实例时。
四、代理模式(Proxy Pattern)
代理模式也就是要使用某个类的行为时,不直接使用他,而是通过另一个类来使用。在代理模式里,有一个代理类和一个被代理类。代理类的作用是接受指令,被代理类的作用是执行。一般在使用的时候,代理类和被代理类要实现同样的接口,同时要把一个被代理类的对象传递给代理类。通过对代理类的调用,最终是被代理类实际工作。我们举一个日常生活很常见的例子,例如明星和经纪人。经纪人是明星的代理人,需要明星做什么,肯定都会通过经纪人传达,而明星是被代理人,是真正去演戏、唱歌的人。
public class DesignPattern {
public static void main(String[] args) {
Star star=new Agent("Jay");//不用直接创建RealStar对象,直接创建一个代理的对象
star.sing();//通过代理对象来执行
star.act();
}
}
interface Star{//Star接口由代理类和被代理类共同实现,这是必须的,否则代理类和被代理类行为不一样,怎么代理呢
public void sing();
public void act();
}
class RealStar implements Star{
private String name;
public RealStar(String name){
this.name=name;
}
@Override
public void sing(){//接口方法的实现
System.out.println(this.name+" is singing");
}
@Override
public void act(){
System.out.println(this.name+" is acting");
}
}
class Agent implements Star{//经纪人类,也就是代理类,实现被代理类一样的接口
private RealStar realStar;//传入的RealStar对象,除了他以外,其他的实现都必须和被代理类相同
private String name;
public Agent(String name){
this.name=name;
}
@Override
public void sing(){//覆盖的接口方法通过被代理类来执行
if(realStar==null)
realStar=new RealStar(name);
realStar.sing();
}
@Override
public void act(){
if(realStar==null)
realStar=new RealStar(name);
realStar.act();
}
}
五、装饰器模式(Decorator Pattern)
装饰器模式允许向一个现有的对象添加新的功能,同时又不改变其结构。这种模式创建了一个装饰类,用来包装原有的类,并在保持类方法签名完整性的前提下,提供了额外的功能。乍一看好像跟代理模式有一点相似,其实不是的。装饰器模式是为了增强功能,代理模式是为了加以控制。装饰器模式可以动态地(继承是静态)给一个对象增加一些额外的职责,主要用于在不想增加很多子类的情况下扩展类。在程序开发中,如果发现你的继承多于两层,就应该反思开发思路是不是有问题了。装饰器模式是对继承的有力补充,他可以替代继承,解决类膨胀的问题。
public class DesignPattern {
public static void main(String[] args) {
Shape normalCircle=new Circle();
Shape redCircle=new RedShapeDecorator(new Circle());//要使用的时候,通过装饰器类把被装饰类包裹起来
Shape redRectangle=new RedShapeDecorator(new Rectangle());
Shape redBlueCircle=new RedShapeDecorator(new BlueShapeDecorator(new Circle()));//装饰了两个颜色
normalCircle.draw();
System.out.println();
redCircle.draw();
System.out.println();
redRectangle.draw();
System.out.println();
redBlueCircle.draw();
System.out.println();
}
}
interface Shape{//Shape接口
public void draw();
}
class Rectangle implements Shape{//两个实现了接口的实体类
@Override
public void draw(){
System.out.println("Shape: Rectangle");
}
}
class Circle implements Shape{
@Override
public void draw(){
System.out.println("Shape: Circle");
}
}
abstract class ShapeDecorator implements Shape{//实现了Shape接口的抽象装饰类。这看起来很像一个代理类
protected Shape decoratedShape;
public ShapeDecorator(Shape decoratedShape){//把实例传递进去
this.decoratedShape=decoratedShape;
}
public void draw(){
decoratedShape.draw();
}
}
class RedShapeDecorator extends ShapeDecorator {//继承了抽象装饰类的实体装饰类
public RedShapeDecorator(Shape decoratedShape) {
super(decoratedShape);
}
@Override
public void draw() {
decoratedShape.draw();
setRedBorder(decoratedShape);//这里为形状加了红色边界。
}
private void setRedBorder(Shape decoratedShape){//设置红色边界
System.out.println("Border Color: Red");
}
}
class BlueShapeDecorator extends ShapeDecorator {//如果要装饰另一样功能,那就再设置一个装饰类
public BlueShapeDecorator(Shape decoratedShape) {
super(decoratedShape);
}
@Override
public void draw() {
decoratedShape.draw();
setRedBorder(decoratedShape);//这里为形状加了蓝色边界。
}
private void setRedBorder(Shape decoratedShape){//设置蓝色边界
System.out.println("Border Color: Blue");
}
}
六、适配器模式(Adapter Pattern)
适配器模式是作为两个不兼容的接口之间的桥梁。它结合了两个独立接口的功能。这种模式涉及到一个单一的类,该类负责加入独立的或不兼容的接口功能。举个真实的例子,读卡器是作为内存卡和笔记本之间的适配器。将内存卡插入读卡器,再将读卡器插入笔记本,这样就可以通过笔记本来读取内存卡。他主要用于对已经正在服役的项目的修补,并不推荐在设计的时候使用。我们来举一个这样的例子。例如,我们有一个实现了MP3Player接口的实体类SingPlayer,它可以播放mp3格式的文件,以及一个实现了MP4Player接口的实体类VedioPlayer,他可以播放mp4格式的文件。如果我们想让SingPlayer播放mp4格式文件,要怎么办呢?我们就创建一个实现了MP3Player接口的适配器类Adapter。在这个适配器类中,存有VedioPlayer对象。也就是说,能播放mp4格式。最后再创建一个实现了MP3Player接口的实体播放器类Player,这个类里有适配器对象。如果要播放mp3格式文件,那就直接用Player播放。如果要播放mp4格式,那就通过适配器来播放。
public class DesignPattern {
public static void main(String[] args) {
MP3Player player=new Player();//通过适配器,就可以用mp3播放器来播放mp4格式的文件了
player.play("mp3", "song1");//说到底,适配器的作用就是作为中介,连接起本来两个不兼容的类型
player.play("mp4", "song2");
}
}
interface MP3Player{//MP3播放器接口
public void play(String type, String fileName);
}
interface MP4Player{//MP4播放器接口
public void play(String type, String fileName);
}
class SingPlayer implements MP3Player{//MP3播放器具体实现类
@Override
public void play(String type, String fileName){
System.out.println("playing->"+"type:"+type+" fileName:"+fileName);
}
}
class VedioPlayer implements MP4Player{//MP4播放器具体实现类
@Override
public void play(String type, String fileName){
System.out.println("playing->"+"type:"+type+" fileName:"+fileName);
}
}
class Adapter implements MP3Player{//适配器类,他只需要适配MP3Player没法播放的类型就行了
private MP4Player mp4Player;
public Adapter(String type){
if(type.equalsIgnoreCase("mp4"))
mp4Player=new VedioPlayer();
}
@Override
public void play(String type, String fileName){
if(type.equalsIgnoreCase("mp4"))
mp4Player.play(type, fileName);
}
}
class Player implements MP3Player{//实体播放器类,他里面包含了一个适配器对象
Adapter adapter;
@Override
public void play(String type, String fileName){
if(type.equalsIgnoreCase("mp3"))//如果是mp3格式,那就正常播放
System.out.println("playing->"+"type:"+type+" fileName:"+fileName);
else{//如果是mp4格式,那就要new一个Adapter,通过这个对象来播放
adapter=new Adapter("mp4");
adapter.play(type, fileName);
}
}
}
七、策略模式(Strategy Pattern)
在策略模式中,一个类的行为或其算法可以在运行时更改。在策略模式中,我们创建表示各种策略的对象和一个行为随着策略对象改变而改变的context 对象。策略对象改变 context 对象的执行算法。在context类中,会包含一个策略对象。根据传入给context对象的策略对象的类型,可以动态地改变context对象的执行算法的效果。
public class DesignPattern {
public static void main(String[] args) {
Context context = new Context(new OperationAdd());//传入不同的策略实例
System.out.println("10 + 5 = " + context.executeStrategy(10, 5));
context = new Context(new OperationSubstract());
System.out.println("10 - 5 = " + context.executeStrategy(10, 5));
context = new Context(new OperationMultiply());
System.out.println("10 * 5 = " + context.executeStrategy(10, 5));
}
}
interface Strategy {//策略接口。后面不同策略的具体实现类会决定context类的行为
public int doOperation(int num1, int num2);
}
class OperationAdd implements Strategy{//后面的加减乘除就是具体的策略类
@Override
public int doOperation(int num1, int num2) {
return num1 + num2;
}
}
class OperationSubstract implements Strategy{
@Override
public int doOperation(int num1, int num2) {
return num1 - num2;
}
}
class OperationMultiply implements Strategy{
@Override
public int doOperation(int num1, int num2) {
return num1 * num2;
}
}
class Context{
private Strategy strategy;
public Context(Strategy strategy){
this.strategy=strategy;
}
public int executeStrategy(int num1, int num2){//传入两个参数,通过传入的不同策略类,来得到不同的结果
return strategy.doOperation(num1, num2);
}
}
八、观察者模式
定义对象间的一种一对多的依赖关系,当一个对象的状态发生改变时,所有依赖于它的对象都得到通知并被自动更新。就像有一个对象是被观察者,很多对象一直在观察他,当这个对象发生变化时,其他观察着他的对象也要做出相应的改变。例如说,在拍卖的时候,拍卖师就是一个被观察的对象。当拍卖师每次叫价,其他竞价者都要观察着他,来决定自己是否跟。相当于一个目标对象(被观察对象)发生变化,所有依赖对象(观察者对象)得到广播通知。
import java.util.ArrayList;
import java.util.List;
public class DesignPattern {
public static void main(String[] args) {
Subject subject = new Subject();
new HexaObserver(subject);
new OctalObserver(subject);
new BinaryObserver(subject);
System.out.println("First state change: 15");
subject.setState(15);//一旦setState,所有观察者立刻做出反应
System.out.println("Second state change: 10");
subject.setState(10);
}
}
class Subject {//Subject就是被观察的对象
private List observers = new ArrayList();//一系列观察者类对象被放进List中保存
private int state;
public int getState() {//state存了一个要进行转换的数字
return state;
}
public void setState(int state) {//一旦setState了,立刻同时通知所有在观察者列表中的观察者,让观察者们立刻把状态全部更新
this.state = state;
notifyAllObservers();
}
public void attach(Observer observer){
observers.add(observer);
}
public void notifyAllObservers(){
for (Observer observer : observers) {
observer.update();
}
}
}
abstract class Observer {//观察者抽象类
protected Subject subject;
public abstract void update();
}
class BinaryObserver extends Observer{//接下来是三个观察者实现类。他们的作用是把被观察对象的状态变为2进制、8进制和16进制的数字字符串
public BinaryObserver(Subject subject){//初始化观察者时要把观察者和被观察者关联起来
this.subject = subject;
this.subject.attach(this);
}
@Override
public void update() {
System.out.println( "Binary String: " + Integer.toBinaryString( subject.getState() ) );
}
}
class OctalObserver extends Observer{
public OctalObserver(Subject subject){
this.subject = subject;
this.subject.attach(this);
}
@Override
public void update() {
System.out.println( "Octal String: " + Integer.toOctalString( subject.getState() ) );
}
}
class HexaObserver extends Observer{
public HexaObserver(Subject subject){
this.subject = subject;
this.subject.attach(this);
}
@Override
public void update() {
System.out.println( "Hex String: " + Integer.toHexString( subject.getState() ).toUpperCase() );
}
}
九、迭代器模式(Iterator Pattern)
这种模式用于顺序访问集合对象的元素,不需要知道集合对象的底层表示。这是Java中非常常用的设计模式。一般都是在一个聚合类中创建迭代器。接下来我们使用一个Container接口和一个Iterator接口实现迭代器模式的例子。
public class DesignPattern {
public static void main(String[] args) {
NameRepository namesRepository = new NameRepository();
for(Iterator iter = namesRepository.getIterator(); iter.hasNext();){//使用迭代器的典型套路
//对容器熟悉的话应该很眼熟
String name = (String)iter.next();
System.out.println("Name : " + name);
}
}
}
interface Iterator {//迭代器接口,要实现迭代器,必须实现这个接口
public boolean hasNext();
public Object next();
}
interface Container {//聚合容器接口
public Iterator getIterator();
}
class NameRepository implements Container {
public String names[] = {"Robert" , "John" ,"Julie" , "Lora"};//用一个数组存储数据
@Override
public Iterator getIterator() {
return new NameIterator();
}
private class NameIterator implements Iterator {//用一个私有类的迭代器
int index;//通过内部的一个index的整形来标志迭代器目前的位置
@Override
public boolean hasNext() {
if(index < names.length){
return true;
}
return false;
}
@Override
public Object next() {
if(this.hasNext()){
return names[index++];
}
return null;
}
}
}
优点: 1、它支持以不同的方式遍历一个聚合对象。 2、迭代器简化了聚合类。 3、在同一个聚合上可以有多个遍历。 4、在迭代器模式中,增加新的聚合类和迭代器类都很方便,无须修改原有代码。
缺点:由于迭代器模式将存储数据和遍历数据的职责分离,增加新的聚合类需要对应增加新的迭代器类,类的个数成对增加,这在一定程度上增加了系统的复杂性。
使用场景: 1、访问一个聚合对象的内容而无须暴露它的内部表示。 2、需要为聚合对象提供多种遍历方式。 3、为遍历不同的聚合结构提供一个统一的接口。