目录
前言
这是我的设计模式笔记持续更新中,设计模式分三类:
结构型:适配器,代理,门面,装饰,组合,桥接
行为型:责任琏,迭代器,命令
创建型:单例,工厂
以下是本篇文章正文内容,下面案例可供参考
一、设计模式七大规则?
- 开闭原则:对扩展开放,对修改关闭
- 单一职责原则:一个类只负责一个功能相应区域
- 里氏代换原则:一个父类可以透明使用子类的所有方法(子类可以去扩展父类的方法,但是不要去修改父类的方法)
- 依赖倒转:依赖抽象不能依赖具体实现(通俗解释就是面向接口编程,把不同的模块抽象成接口,使两个模块能同时进行开发)
- 接口隔离:类之间的关系应该建立在最小的接口(在写接口时如果写的非常多,那么你实现一个接口就要实现大量方法,所以一定要最小接口,方法够用就行)
- 合成聚合复用:尽量使用合成聚合复用而不是继承复用
- 迪米特法则:一个软件的实体类应该尽可能的与其他实体少接触(降低耦合性)
二、常用设计模式理解
1.单例模式
单例模式讲究的就是只能new一个出来,但是这种看起来很简单的只能new一个,里面却复杂的一批,涉及多线程,jvm虚拟机等等
懒汉式
public class sing {
private static sing sing;
private sing(){}
public static sing getSing(){
if(sing != null){
return sing;
} else {
sing = new sing();
System.out.println("new了的单例:"+sing);
}
}
}
return sing;
}
public static void main(String[] args) {
sing sing1 = sing.getSing();
sing sing2 = sing.getSing();
System.out.println(sing1);
System.out.println(sing2);
}
}
这个单例在单线程情况下的确是正常的,可是java本来就是拿来做后端的,一旦引入多线程:
new Thread(() -> {
System.out.println("线程1得到的单例:"+sing.getSing());
}).start();
new Thread(() -> {
System.out.println("线程2得到的单例:"+sing.getSing());
}).start();
这时候你感觉也没问题,但是它已经出问题了。由于我们处理的数据量少,所以第一个线程一下就执行完毕,当第二个线程再进来是判断到的确有了单例就不会继续创建,所以得到的结果是正确的。但是如果处理的数据量大呢?
else {
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
sing = new sing();
System.out.println("new了的单例:"+sing);
}
这里模拟sleep一秒,表述正在处理数据,看看结果:
结果就是由于两个线程由于上一个线程还在处理东西,没及时new出来,第二个线程就进来了。第二个线程判断null于是也new了一个,单例单例肯定是只能new一个出来,这里new了两个肯定是有问题,解决问题也很简单,直接把他变成同步方法+个synchronized关键字:
public synchronized static sing getSing(){
好家伙正常了,但是问题来了,要是每个人每个方法都要过来拿锁,那不是人人都要等着,你已经new了也要等?显然不合理,那么就让new一次的有锁就行了,new完了,这把锁就放掉:
public static sing getSing(){
if(sing != null){
return sing;
}else {
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
synchronized (sing.class){
if(sing == null){
sing = new sing();
System.out.println("new了的单例:"+sing);
}
}
}
return sing;
}
最后为了防止jvm的重排列,我们再在sing前加一个volatile万无一失,这里是否需要volatile字段呢
答案是必须加:解释如下:
这是一个常规的new一个对象,那么它的底层是怎么实现的呢?
0 new #2<T> //开辟一个空间此时并没有与t建立联系,只是开辟空间,此时m=0
3 dup
4 invokespecial #3 <T.int> //调用构造方法,此时m=8,但是还是没有与t建立联系
7 astore_l //此时才与t建立联系
8 return
在第0步与4与7中,7与0,4都没联系,那么如果发生jvm重排序会怎么样: 如果7和4换了位置,那么你先执行0步开辟了一个空间,然后就到第七步t指向了这个没有调构造函数的半初始化对象,如果此时又一个线程进来,首先判断t是否为null,此时t指向一个半初始化的T肯定不为null,然后这个线程就拿这个半初始化的t去用了,这不就芭比q了嘛! 这就是为什么要加volatile防止指令重排序的原因,这种情况一千万次碰不到一次,但是如果是淘宝那种高并发的情况,很有可能发生。 private volatile static sing sing;
饿汉式
饿汉式比较简单,是线程安全的,但是就是无论如何都会new一个对象,消耗性能。
public class Person {
//饿汉式单例
private static Person person = new Person();
private Person(){}
public static Person getInstance(){
return person;
}
}
2.工厂模式
简单工厂:
是一种暴露生产逻辑的生成方式,比较简单:
public class SimpleFactory {
TvFactory tf = new TvFactory();
TV xiaomi = tf.getTv("xiaomi",27);
TV haier = tf.getTv("haier",30);
}
interface TV{
public void showTv();
}
class xiaomiTV implements TV{
protected String name;
protected int size;
public xiaomiTV(String name,int size){
this.name = name;
this.size = size;
}
@Override
public void showTv() {
System.out.println(name+","+size);
}
}
class haierTV implements TV{
protected String name;
protected int size;
public haierTV(String name,int size){
this.name = name;
this.size = size;
}
@Override
public void showTv() {
System.out.println(name+","+size);
}
}
class TvFactory{
public static TV getTv(String name,int size){
TV tv = null;
if("xiaomi".equals(name)){
tv = new xiaomiTV("xiaomi",size);
}else if("haier".equals(name)){
tv = new haierTV("haier",size);
}
return tv;
}
}
工厂方法:
把暴露生产逻辑的交给子类处理,这个也非常简单:
package com.ccsu.shejimoshi.FactoryMethon;
public class FactoryMethon {
public static void main(String[] args) {
xiaomiTV xiaomiTV = (com.ccsu.shejimoshi.FactoryMethon.xiaomiTV) new xiaoTvFactory().getTv(27);
}
}
interface TV{
public void showTv();
}
class xiaomiTV implements com.ccsu.shejimoshi.FactoryMethon.TV {
protected String name;
protected int size;
public xiaomiTV(String name,int size){
this.name = name;
this.size = size;
}
@Override
public void showTv() {
System.out.println(name+","+size);
}
}
class haierTV implements com.ccsu.shejimoshi.FactoryMethon.TV {
protected String name;
protected int size;
public haierTV(String name,int size){
this.name = name;
this.size = size;
}
@Override
public void showTv() {
System.out.println(name+","+size);
}
}
abstract class TvFactory{
protected TV tv;
abstract TV getTv(int size);
}
class xiaoTvFactory extends TvFactory{
@Override
TV getTv(int size) {
this.tv = new xiaomiTV("xiaomi",27);
return tv;
}
}
class haierTvFactory extends TvFactory{
@Override
TV getTv(int size) {
this.tv = new haierTV("haier",30);
return this.tv;
}
}
抽象工厂
抽象工厂跟工厂方法差不多,就是一个工厂生成多个商品,就是相当于在工厂方法上的扩展,就后面如果再来新的产品,就在工厂的父类去加抽象方法,子类去实现就好了。
3.模板方法:
模板方法就是一个继承父类实现的概念,没什么好讲的,应用场景非常多,最典型的就是servlet的doget,dopost请求,自己写一个servlet都需要继承父类servlet然后重写doget或者dopost方法,其实就是一个模板方法。
4.责任链模式:
责任链模式就是一条链,一级一级的,比如说一个项目50000,主管处理不了传给经理,经理处理不了传给总经理,总经理处理不了,就传给总裁,但是注意一点就是,每个人都要继承一个基类,要把公共的方法或者属性写道基类里,避免重复造轮子。
经典运用:拦截器
package com.ccsu.shejimoshi.duty;
public abstract class Man {
protected Man man;
abstract void doIt(int money );
public void setMan(Man man){
this.man = man;
}
}
class oneMan extends Man{
@Override
void doIt(int money) {
if (money < 5000){
System.out.println("oneMan处理了");
}else{
System.out.println("交给下一级处理!");
this.man.doIt(money);
}
}
}
class towMan extends Man{
@Override
void doIt(int money) {
if (money < 10000){
System.out.println("towMan处理了");
}else{
System.out.println("交给下一级处理!");
this.man.doIt(money);
}
}
}
class thrMan extends Man{
@Override
void doIt(int money) {
if (money < 20000){
System.out.println("thrMan处理了");
}else{
System.out.println("交给下一级处理!");
this.man.doIt(money);
}
}
}
class testMain{
public static void main(String[] args) {
Man oneman = new oneMan();
Man towman = new towMan();
Man thrman = new thrMan();
oneman.setMan(towman);
towman.setMan(thrman);
oneman.doIt(9999);
}
}
5、代理模式--aop的灵魂:
对于结构类模式就不得不说,代理模式,中介模式,外观模式,装饰模式
这里面呢,重中之重,spring的aop设计灵魂就是代理模式
静态代理模式:
首先我们来看一张静态代理的图:
分析角色:
卖房:抽象对象,对卖房这个东西进行抽象
开发商:建房的人,卖房的人,开发商建了很多房子要卖,但是开发商主要业务是建房卖房,建了那么多房子,哪有空带买房的人去一个一个看,就直接去下一个工地去间房子了;
销售:销售无法卖房,但是销售可以代表开发商跟你建立买房合同,同时,销售还可以带你看房,开发票
这就是代理模式:
分析代理模式的好处:
1、主类更加纯粹的专注于一件事(开发商只需要疯狂建房子就好了,管你们买不买得起,我只管建)
2、实现业务分工,公共业务交给代理角色
3、方便公共业务的扩展(哪天咱要搞线上vr看房,直接在代理里面加个vr看房就ok了)
代码实现:
package com.ccsu.shejimoshi.dailimoshi;
public class Client {
public static void main(String[] args) {
SaleHouse saleHouse = new host();
proxy proxy = new proxy(saleHouse);
proxy.seeHouse();
proxy.sale();
}
}
//抽象类,卖房
abstract class SaleHouse{
abstract void sale();
}
//开发商
class host extends SaleHouse{
@Override
void sale() {
System.out.println("卖房咯!!!");
}
}
//代理销售
class proxy extends SaleHouse{
private SaleHouse saleHouse;
public proxy(SaleHouse saleHouse){
this.saleHouse = saleHouse;
}
//卖房
@Override
void sale() {
saleHouse.sale();
}
//看房
public void seeHouse(){
System.out.println("咱带你去看房!!!");
}
}
这就是静态代理,相信都看到了静态代理的弊端,就是一个代理只能代理一个对象,那么我们想一个代理代理多个对象呢,王牌销售,这时就要引入动态代理了
动态代理
与静态代理相比,动态代理,改变的是一个类,就是代理类,动态代理类有三种实现机制我这里主要用jdk实现
如图:
与静态代理相比我们可以看到我们的代理类并没有实现sale接口,按理来说我们销售没有实现sale接口这就应该没有办法卖房子了,但是java里有个反射机制,我既然不去实现你的卖房接口,那我可以去反射拿到你开发商的接口嘛,照样可以实现这个功能:
那jdk如何实现这种反射拿到接口方法的呢:
在jdk代理中提供了一个接口:
这个接口中有一个invoke方法:
解释参数:
方法返回类型就是object,这表明待会我们可以用这个返回值做动态:
这个方法顾名思义就是个执行的方法:
传入一个proxy代理对象,传入代理对象要执行的方法method,传入方法需要的参数args
然后调用就是可以执行了
那么问题来了,我们如何获取这个代理对象proxy呢,jdk代理中提供了另一个类用于创建代理对象proxy:
这个类的返回值是一个object这说明我们又可以那这个方法做动态,有个这两个动态,我们就可以做动态代理了,解释参数:
loader就是我们要代理的类的类加载器(jvm知识,要背得滚瓜烂熟),比如说我们的开发商host,这里直接传入host.getclass().getclassloader();就可以了
interfaces就是我们要代理的类的接口,比如host.getclass().getInterfaces()。
invocationhander:这里就是我们之前定义的那个类,这里就是把两个动态链接起来了:
解释:1、将开发商类注入到invocationhandle中,这里使用set注入
2、3、在开发商类被set进invocationhandle的实现类后,就可以拿到开发商了的rent方法和参数
4、将有开发商类的rent方法的invocationhandle的实现类注入到newProxyInstance中。
5、6、将开发商的classloder与interface注入到newProxyInstance中,此时就已经有了一个完整的代理类了
调用rent方法就ok了
代码实现:
package com.ccsu.shejimoshi.dailimoshi.DynamicDaili;
public interface sale {
public void rent();
}
package com.ccsu.shejimoshi.dailimoshi.DynamicDaili;
public class host implements sale{
@Override
public void rent() {
System.out.println("卖房了。。。");
}
}
package com.ccsu.shejimoshi.dailimoshi.DynamicDaili;
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
import java.lang.reflect.Proxy;
public class GetProxy implements InvocationHandler {
private Object target;
public GetProxy(Object target){
this.target = target;
}
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
method.invoke(target,args);
return null;
}
}
package com.ccsu.shejimoshi.dailimoshi.DynamicDaili;
import java.lang.reflect.Proxy;
public class client {
public static void main(String[] args) throws NoSuchMethodException {
sale h = new host();
Class targetClass = h.getClass();
Class[] temp = targetClass.getInterfaces();
GetProxy g = new GetProxy(h);
sale proxy = (sale)Proxy.newProxyInstance(targetClass.getClassLoader(),targetClass.getInterfaces(),g);
proxy.rent();
}
}
这就是一个完整的动态代理模型!
总结
以后会刻意去按照设计模式去编写代码