设计模式实际上就是一种编程思想,尽管之前可能没有听过种种的设计模式但是在实际的开发中我们可能已经在用了。学习这些设计模式的意义在与让我们对各种编程方式有一个更系统的了解。再遇到一些场景时我们可以写出更合乎规范(高内聚、低耦合)的代码。
命令模式
将一个请求封装为一个对象,从而使我们可用不同的请求对客户进行参 数化;对请求排队或者记录请求日志,以及支持可撤销的操作。
命令模式中的角色主要包括:抽象命令类、具体命令类、调用者、接受者、客户端。在这几个角色中接受者的角色根据具体的调用场景是可有可无的。设计模式的优点在于:可以减低系统的耦合度,很容易将命令加入进去,形成一个命令队列或是宏命令。他的缺点同样也是明显的,每一个命令都会创建一个类,大量类的创建会影响命令模式的使用。
角色中抽象命令类、具体命令类、接收者都是好理解的,但是对于调用者还不是很理解,根据实例的代码个人认为调用者是用来编排命令的,命令执行的先后逻辑执行的策略应该是要在调用者角色中完成。调用者角色就是下面代码中的Invoker
public abstract class ICommand {
abstract void execute();
abstract void reDo();
abstract void unDo();
public void log(Receiver receiver,String commandMes){
System.out.println();
}
}
class CommandOpenDoor extends ICommand{
private Receiver receiver;
private String commandMes;
public CommandOpenDoor(Receiver receiver,String commandMes){
this.commandMes = commandMes;
this.receiver = receiver;
}
@Override
public void execute() {
System.out.println("接受者:"+receiver.getId()+";执行了开门操作");
}
@Override
public void reDo() {
System.out.println("reDo操作,再次执行");
execute();
}
@Override
public void unDo() {
System.out.println("接受者:"+receiver.getId()+"执行撤回操作,进行关门");
}
}
class CommandOpenLight extends ICommand {
private Receiver receiver;
private String commandMes;
public CommandOpenLight(Receiver receiver, String commandMes) {
this.receiver = receiver;
this.commandMes = commandMes;
}
@Override
public void execute() {
System.out.println("接受者:"+receiver.getId()+";执行了开灯操作");
}
@Override
public void reDo() {
System.out.println("reDo操作,再次执行");
execute();
}
@Override
public void unDo() {
System.out.println("接受者:"+receiver.getId()+"执行撤回操作,进行关灯");
}
}
public class Invoker {
//命令队列
List<ICommand> commandList = new ArrayList<>();
public Invoker(List<ICommand> commandList) {
this.commandList = commandList;
}
public boolean commandDo(){
try{
for (ICommand item:commandList){
item.execute();
}
return true;
}catch (Exception ex){
ex.printStackTrace();
return false;
}finally {
//根据策略如果需要的话进行撤回操作
}
}
public boolean commandUnDo(){
try{
for (ICommand item:commandList){
item.unDo();
}
return true;
}catch (Exception exception){
exception.printStackTrace();
return false;
}finally {
//根据相应策略做更复杂操作
}
}
}
public class Client {
public static void main(String[] args) {
List<ICommand> awaitCommands = new ArrayList<>();
awaitCommands.add(new CommandOpenDoor(new Receiver("门1","D234132"), "开门操作"));
awaitCommands.add(new CommandOpenLight(new Receiver("灯1","D234232"), "开灯操作"));
Invoker invoker = new Invoker(awaitCommands);
invoker.commandDo();
}
}
public class Receiver {
private String name;
private String id;
}
代理模式
代理模式,Java反射的应用,代理模式来实现一些功能性的增强。代理模式就是在源代码甚至原业务逻辑都不改变的情况下对原有的代码进行一个增强,直接也业务流中切入代码,增强功能。这和Spring的AOP很像。代理模式的应用场景也很多例如:SpringAOP、日志打印、事务控制、权限控制,异常处理。
代理模式的实现也已有很多种,换句话说就是代理的实现有多种:静态代理、动态代理、通过第三方库实现的代理。关于静态代理、动态代理的实现在上面的那票博客中已经有详细的介绍,这里主要对上一篇中缺少部分住一个补充——原生动态代理、通过第三方库cglib来实现的动态代理的原理、使用与区别。
CGLIB原理:CGLIB是一个强大的高性能的代码生成包,底层是通过使用一个小而快的字节码处理框架ASM,它可以 在运行期扩展Java类与实现Java接口,Enhancer是CGLIB的字节码增强器,可以很方便的对类进行拓展 创建代理对象的几个步骤:
1、生成代理类的二进制字节码文件
2、加载二进制字节码,生成Class对象( 例如使用Class.forName()方法 )
3、通过反射机制获得实例构造,并创建代理类对象
JDK动态代理与CGLIB的区别:jdk动态代理:利用拦截器(拦截器必须实现InvocationHanlder)加上反射机制生成一个实现代理接口的匿名类,在调用具体方法前调用InvokeHandler来处理。只能对实现了接口的类生成代理。 cglib是利用ASM开源包,对代理对象类的class文件加载进来,通过修改其字节码生成子类来处理。主要是对指定的类生成一个子类,并覆盖其中方法实现增强,但是因为采用 的是继承,对于final类或方法,是无法继承的。
1. 如果目标对象实现了接口,默认情况下会采用JDK的动态代理实现AOP。
2. 如果目标对象实现了接口,可以强制使用CGLIB实现AOP。
3. 如果目标对象没有实现了接口,必须采用CGLIB库,Spring会自动在JDK动态代理和CGLIB之 间转换。
JDK动态代理代码实现:
public interface IIntroduct {
void hello(String name);
}
public interface IEat {
void eat();
}
public class PersionIntroduct implements IIntroduct,IEat{
@Override
public void hello(String name) {
System.out.println("Persion——Hello,"+name);
}
@Override
public void eat() {
System.out.println("Eating~~~~~~~~~");
}
}
下面的代码中有jdk动态代理代码实现的关键步骤:
public class IntroductProxy implements InvocationHandler {
private Object targetObject;
public IntroductProxy(Object targetObject) {
this.targetObject = targetObject;
}
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
//这才是一种正常的写法,原来的那篇博客中再逐步判断方法面的方式是错误的
System.out.println("Befor~~~~~~~");
Object result = method.invoke(targetObject,args);
System.out.println("After~~~~~~~");
return result;
}
}
public class ProxyTest {
@Test
public void test01(){
PersionIntroduct persionIntroduct = new PersionIntroduct();
IntroductProxy introductProxy = new IntroductProxy(persionIntroduct);
//这里面模式实现的是一个多接口代理,这三个参数分别是:代理对象的类加载器、代理对象需要实现的接口(可以是多个)
//方法调用的实际处理这
IEat proxy = (IEat) Proxy.newProxyInstance(introductProxy.getClass().getClassLoader(),
new Class[]{IIntroduct.class,IEat.class},introductProxy);
// proxy.hello("LiuMenglei");
proxy.eat();
}
}
CGLIB实现的动态代理:
通过CGLIB的Enhancer来指定要代理的目标对象、实际处理代理逻辑的对象,最终通过调用create()方 法得到代理对象,对这个对象所有非final方法的调用都会转发给MethodInterceptor.intercept()方法, 在intercept()方法里我们可以加入任何逻辑,
public class Hello {
public void hello(String name){
System.out.println("我在给你打招呼:"+name);
}
}
public class MyMethodInterceptor implements MethodInterceptor {
@Override
public Object intercept(Object o, Method method, Object[] args, MethodProxy methodProxy) throws Throwable {
System.out.println("前置操作~~~~~~~~~");
return methodProxy.invokeSuper(o,args);
}
}
public class CglibTest {
@Test
public void test01(){
Enhancer enhancer = new Enhancer();
enhancer.setSuperclass(Hello.class);
enhancer.setCallback(new MyMethodInterceptor());
Hello helloObject = (Hello) enhancer.create();
helloObject.hello("LiuMenglei");
}
}
模板方法模式
模板方法就是定义一个算法中的不变部分,扩展可变部分,他的特点在于父类控制行为,子类实现行为。这种设计你模式的有点在于提取公共代码实现共享便于维护。但是在这种模式下每一种不同的实现都需要一个类,如果要是实现过多的时候就会出现大量的类,是系统庞大臃肿。
public abstract class Game {
public void gameExcuate(){
gameBegin();
gamePlay();
gameEnd();
}
abstract void gameBegin();
abstract void gamePlay();
abstract void gameEnd();
}
public class BattlefieldGame extends Game {
@Override
void gameBegin() {
System.out.println("大吉大利,今晚吃鸡");
}
@Override
void gamePlay() {
System.out.println("正在战斗中!!!!!!");
}
@Override
void gameEnd() {
System.out.println("落地成盒,下次努力");
}
}
适配器模式
适配器就是通过讲一个接口适配成客户希望的另一个接口,适配器模式的实现方式推荐通过继承或是依赖实现。 适配器模式适用的场景是(概括为一句话就是类的重复使用。):
1、系统需要使用现有的类,而此类的接口不符合系统的需要。
2、想要建立一个可以重复 使用的类,用于与一些彼此之间没有太大关联的一些类,包括一些可能在将来引进的类一起工作,这些 源类不一定有一致的接口。
3、通过接口转换,将一个类插入另一个类系中。(比如老虎和飞禽,现在 多了一个飞虎,在不增加实体的需求下,增加一个适配器,在里面包容一个虎对象,实现飞的接口。)
优点: 1、可以让任何两个没有关联的类一起运行。 2、提高了类的复用。 3、增加了类的透明度。 4、灵活性好。
缺点: 1、过多地使用适配器,会让系统非常零乱,不易整体进行把握。比如,明明看到调用的是 A 接 口,其实内部被适配成了 B 接口的实现,一个系统如果太多出现这种情况,无异于一场灾难。因此如果 不是很有必要,可以不使用适配器,而是直接对系统进行重构。 2.由于 JAVA 至多继承一个类,所以至 多只能适配一个适配者类,而且目标类必须是抽象类。
public interface AdvancedMediaPlayer {
public void advancePlay(String fileName);
}
public class AdvancedMediaVlc implements AdvancedMediaPlayer{
@Override
public void advancePlay(String fileName) {
System.out.println("通过AdvancedMediaPlayer播放Vlc文件");
}
}
public class AdvanedMediaMp4 implements AdvancedMediaPlayer {
@Override
public void advancePlay(String fileName) {
System.out.println("通过AdvancedMediaPlayer播放Mp4文件");
}
}
体现适配器模式的核心代码:
在适配器MediaAdapter中实现了一节接口,通过这个接口可以规范调用被适配对象时的参数传递,从一定程度上来说两个接口能否被适配取决于要适配方能够提供的参数信息要丰富与被适配的接口对象,这样的话被适配的接口才能够被适配成功
public interface MediaPlayer {
public void play(String audioType,String fileName) throws Exception;
}
public class MediaAdapter implements MediaPlayer {
AdvancedMediaPlayer advancedMediaPlayer = null;
@Override
public void play(String audioType, String fileName) {
System.out.println("通过适配器进行了适配!!!");
if(audioType.equals("mp4")){
advancedMediaPlayer = new AdvanedMediaMp4();
}else{
advancedMediaPlayer = new AdvancedMediaVlc();
}
advancedMediaPlayer.advancePlay(fileName);
}
}
public class AudioPlay implements MediaPlayer {
MediaAdapter mediaAdapter = new MediaAdapter();
@Override
public void play(String audioType, String fileName) throws Exception {
if (audioType.equalsIgnoreCase("mp3")){
System.out.println("通过内置播放器开始播放Mp3文件");
}else if (audioType.equalsIgnoreCase("mp4")||
audioType.equalsIgnoreCase("vlc")){
System.out.println("通过适配器适配合适的播放器!");
mediaAdapter.play(audioType,fileName);
}else{
throw new Exception("不支持当前文件");
}
}
}
装饰器模式
装饰者模式就像代理模式一样也是对现有功能的一个增强,在使用的过程中不用较真到底使用哪个模式,但是如果要是非要从文字的层面上甄别的话就是:代理模式实现的是对功能的增强也可以理解为添加新功能,但是适配器模式的核心不是增加新功能而是对已有功能修饰,增强已有的功能。
优点:装饰类和被装饰类可以独立发展,不会相互耦合,装饰模式是继承的一个替代模式,装饰模式可以动态扩展一个实现类的功能。
缺点:多层的装饰比较复杂
public interface Shape {
void draw();
}
public class Circle implements Shape {
@Override
public void draw() {
System.out.println("Shape:Cricle");
}
}
这里代码书写是照着课堂上来的但是对于为什么要再实现一个ShapeDecoratot并不是很理解,这样做的目的是为了规范一个decorateShpe吗,这样写出的代码看着跟规范但是也更复杂,通过一个ShapeDecorator的装饰抽象类实现,来规定了构造函数必须创建来一个要被修饰增强的对象。
public abstract class ShapeDecorator implements Shape{
protected Shape decoratedShape;
public ShapeDecorator(Shape decoratedShape){
this.decoratedShape = decoratedShape;
}
public void draw(){
decoratedShape.draw();
}
}
public 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!");
}
}
观察者模式
意图:一个对象状态改变给其他对象通知的问题,而且要考虑到易用和低耦合,保证高度的协作。 关键代码:在抽象类里有一个 ArrayList 存放观察者们。
优点: 1、观察者和被观察者是抽象耦合的。 2、建立一套触发机制。
缺点: 1、如果一个被观察者对象有很多的直接和间接的观察者的话,将所有的观察者都通知到会花费 很多时间。 2、如果在观察者和观察目标之间有循环依赖的话,观察目标会触发它们之间进行循环调 用,可能导致系统崩溃。 3、观察者模式没有相应的机制让观察者知道所观察的目标对象是怎么发生变化的,而仅仅只是知道观察目标发生了变化。
public class Subject {
private List<Observer> observers = new ArrayList<>();
private int state;
public int getState(){
return state;
}
public void setState(int state){
this.state = state;
notifyAllObservers();
}
public void attact(Observer observer){
observers.add(observer);
}
private void notifyAllObservers(){
for (Observer observer:observers){
observer.update();
}
}
}
public abstract class Observer {
protected Subject subject;
public abstract void update();
}
public class BinaryObserver extends Observer{
public BinaryObserver(Subject subject){
this.subject = subject;
this.subject.attact(this);
}
@Override
public void update() {
System.out.println("Binary String:"+Integer.toBinaryString(subject.getState()));
}
}
测试代码:
每创建一个观察者都指定一个他要观察的对象是什么,代码会新创建的这个观察者添加到这个被观察者对象的观察者队列中,当被观察者中有什么改动的时候就会通过遍历的形式及时的通知所有他的观察者。
public class ObserverTest {
@Test
public void test01(){
Subject subject = new Subject();
new BinaryObserver(subject);
new BinaryObserver(subject);
System.out.println("First state change:15");
subject.setState(15);
System.out.println("Second state change:14");
subject.setState(14);
}
}