设计模式
Posted yanhui007
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了设计模式相关的知识,希望对你有一定的参考价值。
设计原则
装饰模式
动态代理
命令模式
工厂模式
策略模式
委派模式
设计原则
开闭原则、里氏替换原则、依赖倒转原则、接口隔离原则、最少知道原则、单一职责原则、合成复用原则
开闭原则
对修改关闭,对扩展开发。
里氏替换原则
子类可以扩展父类的功能,但是不能改变父类原有的功能。比如子类可以覆盖父类的抽象方法(抽象方法在父类中没有实现),但是不能覆盖父类的非抽象方法(非抽象方法属于父类的原有功能,子类覆盖相当与改变父类的功能)
依赖倒转原则
模块之间依赖接口编程,不能依赖实现编程。
接口隔离原则和单一职责原则
接口隔离原则:细化接口,不要建立臃肿的接口。
单一职责原则:类的功能单一。
这两个原则咋一看挺像的,都是强调类/接口的设计要单一。接口隔离原则指的是接口设计方面。单一职责指的是业务方面。
最少知道原则
高内聚、低耦合
合成复用原则
尽量使用聚合、组合的方式,而不是使用继承。
装饰模式
给类增加功能可以通过几种方式?
1、直接修改类的代码。 违背开闭原则。
2、增加子类。 影响类的继承链。
3、通过装饰模式动态给类增加功能。
优点:扩展性更强,装饰类为可插拔
缺点:会产生多余的小对象(装饰类),相对继承,出现问题排查起来更困难。
Component:最上层接口
ConcreteComponent:被装饰类
Decorator:装饰器接口
ConcreteDecorator:装饰器实现类
public interface Human {
void eat();
}
public class Man implements Human{
public void eat(){
System.out.println("eat");
}
}
public abstract class Decorator implements Human{
private Human human;
public Decorator(Human human){
this.human = human;
}
public void eat(){
human.eat();
}
}
public class ConcreteDecorator extends Decorator{
public ConcreteDecorator(Human human){
super(human);
}
private void wash(){
System.out.println("wash");
}
@Override
public void eat() {
this.wash();
super.eat();
}
}
public class Main {
/***
* 设计模式 -- 装饰模式
* 人类都会吃饭,通过装饰模式,给吃饭增加一个洗手的功能
* @param args
*/
public static void main(String[] args) {
Human man = new Man();
Decorator d = new ConcreteDecorator(man);
d.eat();
}
}
动态代理
介绍三部分内容:静态代理、jdk动态代理、cglib
静态代理
被代理接口
public interface HelloInterface {
void sayHello();
}
被代理类
public class Hello implements HelloInterface{
@Override
public void sayHello() {
System.out.println("Hello zhanghao!");
}
}
代理类
public class HelloProxy implements HelloInterface{
private HelloInterface helloInterface = new Hello();
@Override
public void sayHello() {
System.out.println("Before invoke sayHello" );
helloInterface.sayHello();
System.out.println("After invoke sayHello");
}
}
在编译时就要为每个被代理类编写一个代理类,这样如果有多个被代理类的,就需要写多个代理类。接下来动态代理解决这个问题。
jdk动态代理
被代理接口
public interface UserService {
public String execute() throws Throwable ;
}
被代理类
public class UserServiceImpl implements UserService{
@Override
public String execute() throws Throwable {
System.out.println("step 2 执行方法啦!!");
return "step 2 执行方法啦!!";
}
}
自定义拦截器
public class MyInvocationHandler implements InvocationHandler {
private UserService userService;
public MyInvocationHandler(UserService userService) {
this.userService = userService;
}
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
before(); //事务开启
method.invoke(userService, args);
after(); //事务关闭
return "成功了";
}
private void before() {
System.out.println("事务开启!");
}
private void after() {
System.out.println("事务关闭");
}
}
运行
public class MyTest {
public static void main(String[] args) throws Throwable {
System.out.println("---------------JDK动态代理----------------");
UserService userService = (UserService) Proxy.newProxyInstance(MyTest.class.getClassLoader(),//指定由哪个加载器来加载代理类
new Class<?>[]{UserService.class},//指定被代理的接口,生成的代理类会实现该接口
new MyInvocationHandler(new UserServiceImpl()));
userService.execute();
}
}
当有多个被代理类(这些被代理类都实现相同接口)时,只需要定义一个自定义拦截器,该拦截器持有被代理接口的引用,然后在运行阶段根据传入的具体是哪个被代理类生成代理类。
流程:
1、使用的时候首先创建被代理类,被代理类要实现接口
2、创建自己的MyInvocationHandler实现Jdk的InvocationHandler,在里边增加拦截逻辑,同时把被代理类传递进去
3、通过Proxy的newProxyInstance方法创建代理类,需要将被代理接口和MyInvocationHandler传递进去。
newProxyInstance里边是如何生成代理类的?
首先根据传入的被代理接口获取到class信息,然后获取到构造方法,然后拿着构造方法通过反射生成代理类(这个过程把自定义拦截器也传递进去了,这样代理类中就有了自定义拦截器的引用,可以调用自定义拦截器的方法)。
4、当调用时,实际调用的是代理类的方法,而代理类内部通过反射获取到被代理类要执行的方法,然后传递给MyInvocationHandler,MyInvocationHandler内部先执行拦截逻辑,然后通过反射执行被代理类的方法
原理
生成代理类方法
public static Object newProxyInstance(ClassLoader loader, Class<?>[]interfaces,InvocationHandler h) throws IllegalArgumentException {
if (h == null) {
throw new NullPointerException();
Class cl = getProxyClass(loader, interfaces);
// 通过反射获取构造函数对象并生成代理类实例
try {
//获取构造方法的时候将
Constructor cons = cl.getConstructor(constructorParams);
return (Object) cons.newInstance(new Object[] { h });
} catch (NoSuchMethodException e) { throw new InternalError(e.toString());
} catch (IllegalAccessException e) { throw new InternalError(e.toString());
} catch (InstantiationException e) { throw new InternalError(e.toString());
} catch (InvocationTargetException e) { throw new InternalError(e.toString());
}
}
生成的代理类经过反编译后代码
/**
*代理类也实现了Person接口,看起来和静态代理的方式也会一样的
*同时代理类也继承了Proxy类
*/
public final class $Proxy0 extends Proxy implements Person{
private static Method m4;
private static Method m1;
private static Method m0;
private static Method m3;
private static Method m2;
public $Proxy0(InvocationHandler paramInvocationHandler)
throws
{
super(paramInvocationHandler);
}
//实现了Person接口的方法,这就是我们调用这个方法Proxy.newProxyInstance必须提供第二个参数的作用
public final void sayGoodBye(boolean paramBoolean, double paramDouble)
throws
{
try
{
// 我们看到通过调用代理类的方法时,最终方法都会委托给InvocationHandler实现类的invoke方法
// m4为代理类通过反射获得的Method
this.h.invoke(this, m4, new Object[] { Boolean.valueOf(paramBoolean), Double.valueOf(paramDouble) });
return;
}
catch (Error|RuntimeException localError)
{
。
。
。
。
。
catch (Throwable localThrowable)
{
throw new UndeclaredThrowableException(localThrowable);
}
}
static
{
try
{//代理类通过反射 获得的接口方法Method
m4 = Class.forName("com.yujie.proxy.dynamic.Person").getMethod("sayGoodBye", new Class[] { Boolean.TYPE, Double.TYPE });
m1 = Class.forName("java.lang.Object").getMethod("equals", new Class[] { Class.forName("java.lang.Object") });
m0 = Class.forName("java.lang.Object").getMethod("hashCode", new Class[0]);
m3 = Class.forName("com.yujie.proxy.dynamic.Person").getMethod("sayHello", new Class[] { Class.forName("java.lang.String"), Integer.TYPE });
m2 = Class.forName("java.lang.Object").getMethod("toString", new Class[0]);
return;
}
catch (NoSuchMethodException localNoSuchMethodException)
{
throw new NoSuchMethodError(localNoSuchMethodException.getMessage());
}
catch (ClassNotFoundException localClassNotFoundException)
{
throw new NoClassDefFoundError(localClassNotFoundException.getMessage());
}
}
cglib
如何使用
public class SampleClass {
public void test(){
System.out.println("hello world");
}
public static void main(String[] args) {
Enhancer enhancer = new Enhancer();
enhancer.setSuperclass(SampleClass.class);//指定被代理类
enhancer.setCallback(new MethodInterceptor() {//自定义拦截器
@Override
public Object intercept(Object obj, Method method, Object[] args, MethodProxy proxy) throws Throwable {
System.out.println("before method run...");
Object result = proxy.invokeSuper(obj, args);此处与java proxy有区别
System.out.println("after method run...");
return result;
}
});
SampleClass sample = (SampleClass) enhancer.create();//代理类,生成的代理类会继承被代理类,因为final类型的类无法被继承,所以final类服务通过cglib生成代理
sample.test();
}
}
cglib的实现与java Proxy的本质区别
java Proxy是通过反射不用解释。cglib底层是通过asm实现的。
asm是什么?
java代码从编译到执行的流程如图:
asm做的就是动态生成class文件,放入装载器。
原理分析
被代理类每个方法会生成2个方法,例如被代理类有个方法g(),则在代理类中对应两个方法g()和CGLIB$g$0()。
流程:1、测试代码中的 sample.test();
2、直接调用到代理类的g()
3、因为代理类持有自定义拦截器的引用,所以可以直接调用自定义拦截器的intercept方法。
4、执行自定义拦截器的intecept逻辑。
注意自定义拦截器中:Object result = proxy.invokeSuper(obj, args);
此处与java proxy有区别,此处是调用的父类的方法(生成的代理类集成被代理类),而java proxy中是通过反射调用的被代理类的方法。为什么这么设计?
因为反射效率低,这里采用的是fastClass机制。
fastCLass机制大致意思就是为类中的每个方法加个索引以提高效率。
直接看生成的代理类
public class Target$$EnhancerByCGLIB$$788444a0 extends Target implements Factory
{
private boolean CGLIB$BOUND;
private static final ThreadLocal CGLIB$THREAD_CALLBACKS;
private static final Callback[] CGLIB$STATIC_CALLBACKS;
private MethodInterceptor CGLIB$CALLBACK_0;
private static final Method CGLIB$g$0$Method;
private static final MethodProxy CGLIB$g$0$Proxy;
private static final Object[] CGLIB$emptyArgs;
private static final Method CGLIB$f$1$Method;
private static final MethodProxy CGLIB$f$1$Proxy;
static void CGLIB$STATICHOOK1()
{
CGLIB$THREAD_CALLBACKS = new ThreadLocal();
CGLIB$emptyArgs = new Object[0];
Class localClass1 = Class.forName("net.sf.cglib.test.Target$$EnhancerByCGLIB$$788444a0");
Class localClass2;
Method[] tmp60_57 = ReflectUtils.findMethods(new String[] { "g", "()V", "f", "()V" }, (localClass2 = Class.forName("net.sf.cglib.test.Target")).getDeclaredMethods());
CGLIB$g$0$Method = tmp60_57[0];
CGLIB$g$0$Proxy = MethodProxy.create(localClass2, localClass1, "()V", "g", "CGLIB$g$0");
CGLIB$f$1$Method = tmp60_57[1];
CGLIB$f$1$Proxy = MethodProxy.create(localClass2, localClass1, "()V", "f", "CGLIB$f$1");
}
final void CGLIB$g$0()
{
super.g();
}
public final void g()
{
MethodInterceptor tmp4_1 = this.CGLIB$CALLBACK_0;
if (tmp4_1 == null)
{
CGLIB$BIND_CALLBACKS(this);
tmp4_1 = this.CGLIB$CALLBACK_0;
}
if (this.CGLIB$CALLBACK_0 != null) {
tmp4_1.intercept(this, CGLIB$g$0$Method, CGLIB$emptyArgs, CGLIB$g$0$Proxy);
}
else{
super.g();
}
}
}
命令模式
/***
* 命令模式:
* 命令模式将“请求”封装成对象,以便使用不同的请求、队列或者日志来参数化其他对象。
* 将“行为请求者”与“行为实现者”解耦
*
* 命令模式涉及到的角色:
* commond:命令接口
* concreteCommond:命令实现类
* reciever:真正负责执行命令的类
* invoker:负责调用的类
* client:负责产生命令的人
*
* 模拟餐厅点菜场景:
* 顾客(client)来餐厅点菜,点了西红柿炒鸡蛋(concreteCommond)、
* 宫保鸡丁(concreteCommond)、米饭(concreteCommond),
* 服务员记录下来小单子,
* 将小单子交给厨师(invoker)一个接一个的做菜(reviever)
*
*/
public class Main {
public static void main(String[] args) {
List<Commond> commondList = new ArrayList<>();
Commond c1 = new XhscjdCommond(new XhscjdReciever());
Commond c2 = new GbjdCommond(new GbjdReciever());
Commond c3 = new MfCommond(new MfReciever());
commondList.add(c1);
commondList.add(c2);
commondList.add(c3);
Invoker invoker = new Invoker(commondList);
invoker.invoke();
}
}
请求封装成命令,一个请求一个命令,命令持有reciever的引用,最终执行是reciever执行的
public interface Commond {
void execute();
}
public class GbjdCommond implements Commond{
private Reciever reciever;
GbjdCommond (Reciever reciever){
this.reciever = reciever;
}
@Override
public void execute() {
reciever.action();
}
}
public class XhscjdCommond implements Commond{
private Reciever reciever;
XhscjdCommond (Reciever reciever){
this.reciever = reciever;
}
@Override
public void execute() {
reciever.action();
}
}
public class MfCommond implements Commond{
private Reciever reciever;
MfCommond (Reciever reciever){
this.reciever = reciever;
}
@Override
public void execute() {
reciever.action();
}
}
public abstract class Reciever {
abstract void action();
}
public class XhscjdReciever extends Reciever{
@Override
public void action() {
System.out.println("做西红柿炒鸡蛋");
}
}
public class GbjdReciever extends Reciever{
@Override
public void action() {
System.out.println("做宫保鸡丁");
}
}
public class MfReciever extends Reciever{
@Override
public void action() {
System.out.println("做米饭");
}
}
命令有了,并且命令也持有reciever的引用, 但是由谁来发起对命令的执行调用呢?
public class Invoker{
private List<Commond> commondList;
Invoker(List<Commond> commondList){
this.commondList = commondList;
}
public void invoke() {
for(Commond c:commondList){
c.execute();
}
}
}
工厂模式
简单工厂
工厂方法
抽象工厂方法
简单工厂
产品类
abstract class Product {
abstract void intro();
}
public class AProduct extends Product{
@Override
void intro() {
System.out.println("可乐");
}
}
public class BProduct extends Product{
@Override
void intro() {
System.out.println("奶茶");
}
}
public class CProduct extends Product{
@Override
void intro() {
System.out.println("咖啡");
}
}
工厂类
public class Factory {
public static Product getProduct(String type) {
switch (type) {
case "A":
return new AProduct();
case "B":
return new BProduct();
case "C":
return new CProduct();
default:
return null;
}
}
}
main方法
public class Test {
public static void main(String[] args) {
//创建具体的工厂
Factory factory = new Factory();
//根据传入的参数生产不同的产品实例
//(按下不同的按钮,获取饮料)
Product A = Factory.getProduct("A");
A.intro();
Product B = Factory.getProduct("B");
B.intro();
Product C = Factory.getProduct("C");
C.intro();
}
}
当增加产品的时候需要修改工厂类的代码,解决此问题接下来看工厂方法模式。
工厂方法模式
产品类同上
工厂类
abstract class Factory {
abstract Product getProduct();
}
public class FactoryA extends Factory{
@Override
Product getProduct() {
return new ProductA();
}
}
public class FactoryB extends Factory{
@Override
Product getProduct() {
return new ProductB();
}
}
main方法
public class Test {
public static void main(String[] args) {
//创建具体的工厂
FactoryA factoryA = new FactoryA();
//生产相对应的产品
factoryA.getProduct().intro();
FactoryB factoryB = new FactoryB();
factoryB.getProduct().intro();
}
}
工厂方法对工厂类做了一层抽象,当需要增加新产品的时候不是改工厂类代码,而是增加删除工厂类。
工厂类的缺点是类太多(一个产品对应一个类),解决此问题继续看抽象工厂方法(一个工厂生产多个产品)。
抽象工厂
产品类(增加了一些产品,方便观看)
abstract class Product {
abstract void intro();
}
abstract class ProductA extends Product{
@Override
abstract void intro();
}
abstract class ProductB extends Product{
@Override
abstract void intro();
}
public class ProductAa extends ProductA{
@Override
void intro() {
System.out.println("矿泉水");
}
}
public class ProductBb extends ProductB{
@Override
void intro() {
System.out.println("面包");
}
}
工厂类
abstract class Factory {
//生产饮料
abstract Product getProductA();
//生产零食
abstract Product getProductB();
}
public class FactoryA extends Factory{
@Override
Product getProductA() {
//生产矿泉水
return new ProductAa();
}
@Override
Product getProductB() {
//生产面包
return new ProductBb();
}
}
main方法
public class Test {
public static void main(String[] args) {
//创建零食售卖机(具体工厂),
FactoryA factoryA = new FactoryA();
//获取矿泉水与面包(具体产品)
factoryA.getProductA().intro();
factoryA.getProductB().intro();
}
}
策略模式
将一系列的算法封装起来,一个context持久这些算法类,context与客户端打交道,客户端告诉context我要选择哪种算法。
策略模式和工厂方法模式区别:都是客户端传递一个标识,调用不同的代码分支。
区别是策略模式执行的是一系列算法,工厂方法返回一个产品。
策略模式客户端必须要了解具体的算法,传递给context类。
实现一个计算机
首先定义一个策略的公共接口
public interface Strategy {
int count(int a,int b);
}
然后加、减法分别实现该接口
public class AddStrategy implements Strategy{
@Override
public int count(int a, int b) {
return a+b;
}
}
public class DivStrategy implements Strategy{
@Override
public int count(int a, int b) {
return a-b;
}
}
然后定义一个策略持有类,该类负责与客户端打交道,选择具体的策略
public class StrategryContext {
private Strategy s ;
public StrategryContext(Strategy a){
s = a;
}
public int doCount(int a,int b ){
return s.count(a,b);
}
}
main方法
public static void main(String[] args) {
StrategryContext context = new StrategryContext(new AddStrategy());
System.out.println(context.doCount(2,3));
}
委派模式
委派模式和策略模式又很像。区别是:
委派模式context封装所有的算法,而客户端不需要了解算法,通过一个标识告诉context调用哪个算法就可以。
领导通过context把活委派给zhangsan和lisi,zhangsan是前端,lisi是后端。
public interface Employee {
void work();
}
public class Lisi implements Employee {
@Override
public void work() {
System.out.println("后端");
}
}
public class Zhangsan implements Employee {
@Override
public void work() {
System.out.println("前端");
}
}
public class Context {
private static Map<String,Employee> map = new HashMap<>();
static{//需要把所有的员工都封装到context中,客户端调用的时候不需要知道员工,只要说想干嘛,这里来映射
map.put("前端的活",new Zhangsan());
map.put("后端的活",new Lisi());
}
public void doWork(String work){
map.get(work).work();
}
}
public static void main(String[] args) {
Context c = new Context();
c.doWork("前端的活");
}
以上是关于设计模式的主要内容,如果未能解决你的问题,请参考以下文章