为什么要使用设计模式:是因为他们在一定程度上满足了以下六个原则:
-
单一原则:一个类或者一个方法负责一项原则(复用性提高)
-
里氏替换原则:子类可以扩展父类的功能,但不改变原有父类的功能
-
依赖倒置原则:面向接口编程(通过接口参数作为参数实现应用场景),变量或者参数传递,应该尽量使用抽象类,或者接口
-
接口隔离:建立单一接口,复杂的接口应该拆分成多个简单接口,使得接口设计的粒度变小,系统就变得越灵活(但系统的结构复杂度会提高,开发难度加大)
-
迪米特原则:最少知道原则,降低类与类之间的耦合
-
开闭原则:对扩展开放,对修改闭合
初学者为啥要学设计模式:
-
读懂别人写的代码
-
面试会问
一,单例模式Singleton
定义:保证一个类只有一个实例,并提供全局访问点
应用场景:重量级对象,如数据库连接池,线程池,RunTime类,DefaultSingleTon
代码实现:(内部类形式,懒汉形式,饿汉形式,三种方法实现)
package cn.itcast.test.SingleTon;
import java.io.*;
import java.lang.reflect.Constructor;
import java.lang.reflect.InvocationTargetException;
public class HungrySingleTonTest {
public static void main(String[] args) throws NoSuchMethodException, IllegalAccessException, InvocationTargetException, InstantiationException, IOException, ClassNotFoundException {
//内部类模式检测两个实例化对象是否一样,多线程情况下是否不安全
new Thread(()->{
InnerSingleTon instance1=InnerSingleTon.getInstance();
System.out.println(instance1);
}).start();
new Thread(()->{
InnerSingleTon instance2=InnerSingleTon.getInstance();
System.out.println(instance2);
}).start();
//懒汉模式检测两个实例化对象是否一样,多线程情况下是否不安全
new Thread(()->{
LazySingleTon instance1=LazySingleTon.getInstance();
System.out.println(instance1);
}).start();
new Thread(()->{
LazySingleTon instance2=LazySingleTon.getInstance();
System.out.println(instance2);
}).start();
//饿汉模式检测两个实例化对象是否一样,多线程情况下是否不安全
new Thread(()->{
HungrySingleTon instance1=HungrySingleTon.getInstance();
System.out.println(instance1);
}).start();
new Thread(()->{
HungrySingleTon instance2=HungrySingleTon.getInstance();
System.out.println(instance2);
}).start();
}
}
class HungrySingleTon{
/**
* 饿汉模式
* 静态成员什么时候被加载?
* 当我们调用这个HungrySingleTon类的时候,jvm会判断这个类是否在内存中,
*/
private static HungrySingleTon instance=new HungrySingleTon();
private HungrySingleTon(){
}
public static HungrySingleTon getInstance(){
return instance;
}
}
class InnerSingleTon implements Serializable {
static final long serialVersionUID = 42L;
/**
* 静态内部类的形式的单例模式
* 利用类的加载机制来确定线程的安全
*/
private static class InnerSingleTonHolder {
private static InnerSingleTon instance=new InnerSingleTon();
}
private InnerSingleTon(){
}
public static InnerSingleTon getInstance(){
return InnerSingleTonHolder.instance;//执行这条语句时导致,静态内部类创建,instance才被实例化
}
}
/**
* 懒汉模式的类
*/
class LazySingleTon{
private static volatile LazySingleTon instance;
private LazySingleTon(){
}
//synchronized用来加锁,防止多线程创建对个实例,但是会对性能造成一些损耗
public static synchronized LazySingleTon getInstance1(){//第一种实例化方法
if(instance==null){
try {
Thread.sleep(200);
} catch (InterruptedException e) {
e.printStackTrace();
}
instance=new LazySingleTon();
}
return instance;
}
//上面的方法实例化在多线程下不安全,进行加锁
public static LazySingleTon getInstance(){//第二种实例化方法
if(instance==null){
try {
Thread.sleep(200);
} catch (InterruptedException e) {
e.printStackTrace();
}
synchronized(LazySingleTon.class){
if (instance==null){
instance=new LazySingleTon();
/**从字节码层面分析对象创建的过程(jit编译器,cpu对指令重排序问题)
* 1,分配空间,2,初始化,3,引用赋值。
* 初始化和引用赋值的顺序可能会调换,如果执行顺序调换在多线程下,第一个线程执行了引用赋值
* 第二个线程到达if(instance==null)发现实例存在直接返回对象,但对象还没有完成初始化,就可能造成空指针异常
* 解决:在要实例化的对象上加 volatile
*/
}
}
}
return instance;
}
}
二,工厂模式
1,简单工厂模式
定义:有一个产品接口A,有b1,b2,b3,,,等产品实现了这个接口(也就是满足接口的规范),把这些产品类的对象交给 一个工厂类来创建,根据传入的参数创建对应的类(后期的开发过程中,创建一个对象并对他进行初始化的过程很繁琐,可以把创建对象的过程写在工厂类的方法中,每当需要相关对象时就就调用相关的对象就ok了,不需要写重复的代码。)——》对象创建过程的复用
优点:实现对象的创建和调用的简单分离,创建交给工厂类,客户端程序员只需要关心如何去使用对象。
缺点:工厂类不够灵活,每新增一个产品就要修改工厂类,产品很多的话,实现逻辑就会非常的复杂。
代码实现
package cn.itcast.test.Factory;
public class SimpleFactory {
public static IProduct produceInstance( String type){//代理生产产品对象的工厂
if(type.equals("A")){
return new ProductA();
}
else if(type.equals("B")){
return new ProductB();
}
else {
return null;
}
}
public static void main(String[] args) {
IProduct instance = SimpleFactory.produceInstance("B");//根据穿入的参数不同,创建对应产品的对象
instance.produce();
}
}
interface IProduct{//定义产品的规范
void produce();
}
class ProductA implements IProduct{//具体的产品A
@Override
public void produce() {
System.out.println("生产A");
}
}
class ProductB implements IProduct{//具体的产品B
@Override
public void produce() {
System.out.println("生产B");
}
}
2,工厂模式
定义:定义一个创建对象的接口,让其子类自己决定实例化哪一个工厂类,工厂模式使其创建过程延迟到子类进行(第一次完全看不懂)
说人话:简单工厂模式,存在的问题是工厂类不够灵活,每新增一个产品就要修改工厂类,产品很多的话,实现逻辑就会非常的复杂。所以只使用一个工厂类去创建所有产品的方式是不好的不易于扩展
于是我们采用多工厂去创建产品,一个工厂只创建一个产品,把所有的工厂类都需要遵循工厂接口的规范(拥有创建对象的方法)。这样没增加一个产品我们就创建一个生产该产品的产品类遵循工厂接口的规范
虽然看似要创建很多工厂变复杂了,但我们讲究扩展大于修改。
上图中:LinkList 和 ArrayList是工厂,Collection是工厂接口,定义了这两个类要实现的方法。
Iterator是产品接口规定了产品的类型,ListItr和Itr是具体的产品。他们分别由LinkList 和 ArrayList生产对象
代码实现:
package cn.itcast.test.Factory;
public class SimpleFactory {//测试
public static void main(String[] args) {
IFactory factory=new FactoryA();
IProduct produceA = factory.produceInstance();
produceA.print();
}
}
interface IProduct{//定义产品的规范
void print();
}
class ProductA implements IProduct{//具体的产品A
@Override
public void print() {
System.out.println("生产A");
}
}
class ProductB implements IProduct{//具体的产品B
@Override
public void print() {
System.out.println("生产B");
}
}
//工厂的接口
interface IFactory{
IProduct produceInstance();
}
//生产A产品的工厂
class FactoryA implements IFactory {
public IProduct produceInstance(){//代理生产产品对象的工厂
return new ProductA();
}
}
//生产B产品的工厂
class FactoryB implements IFactory {
public IProduct produceInstance(){//代理生产产品对象的工厂
return new ProductB();
}
}
三,装饰器模式
功能:动态地给一些对象添加额外的功能,就功能而言装饰器模式比,生成一个子类来继承原有类来进行功能的扩展来的更灵活。
因为继承机制是静态的需要在类的层面上修改,而装饰器模式是作用在对象上的。
例如:箱子只能放东西,我们买回来的一个箱子是一个对象,我们自己在箱子外面套上一个安装轮子的壳(装饰器),于是箱子就变成了行李箱(功能得到加强)
代码实现:
package cn.itcast.test.DecoratorPattern;
public class Decorator {//测试
public static void main(String[] args) {
Box box=new FirstBox();
box.doSomething();
System.out.println("------------------------------");
BoxDecorator decorator=new BoxDecorator(box);
decorator.doMoreSomething();
}
}
interface Box{//箱子接口
void doSomething();
}
class FirstBox implements Box{//第一代箱子
@Override
public void doSomething() {
System.out.println("我可以放衣服");
}
}
class BoxDecorator implements Box{//装饰器
private Box box;
public BoxDecorator(Box box){//装饰器初始化的时候把箱子对象传进来,进行功能的加强
this.box=box;
}
@Override
public void doSomething() {
box.doSomething();
}
public void doMoreSomething() {//功能加强后的方法
box.doSomething();
System.out.println("我有轮子,可以拉着走");
}
}
四,适配器模式:
定义:手机充电器就是一个电源适配器,它的作用是将220V的交流电转成5V的直流电,使得手机可以充电。使用适配器就是对接手机充电接口 和 电源接口的作用。
将一个类的接口(220V交流电源接口)变成客户端所期待的另一种接口(5V直流电源接口),使得原本不能在一起工作的两个类能够一起工作(充电)
代码案例:
package cn.itcast.test.AdaptorPattern;
public class AdapterTest {
public static void main(String[] args) {
String power = new Adapter(new PowerSupply()).transferPower();
System.out.println(power);
}
}
//电源
class PowerSupply{
public String output(){
return "220交流电";
}
}
//充电器的接口规定有一个转化电流的方法
interface Transfer{
public String transferPower();
}
//充电器,对传入对象的值进行修改。这点和装饰器不一样,装饰器是进行功能的增强而不是修改
class Adapter implements Transfer{
private PowerSupply power;
Adapter(PowerSupply power){
this.power=power;
}
@Override
public String transferPower() {
String output = power.output();
//经过一系列的机制将200v的交流转成5v的直流电
output="我是5v的交流电";
return output;
}
}
五,观察者模式:
定义:它定义了对象间的一种一对多的依赖关系,使得当一个对象状态发生改变时,其相关的依赖对象都会收到通知并自动被更新
例子:苹果手机专卖店生意很好,有好多客户都来买手机很快手机就没货了,客户还是源源不断的到来,一问没有手机了,它就回去了过了一天再去手机店看看有没有,这样的效率很低。所以店长就和客户说你把电话号码留下,手机有货了,我通知你来买。
代码实现:
package cn.itcast.test.ObserverPattern;
import java.util.ArrayList;
import java.util.List;
public class ObserverTest {
public static void main(String[] args) {
Kite kite=new Kite();
kite.sellPhone(new Jane());//kite把手机买给java发现没货,就留下他们的电话
kite.sellPhone(new kellen());
//kite店长自己判断是否有货
kite.setIfGoods(true);//假设有货
if(kite.isIfGoods()){
kite.notifyCustomer();//打电话通知所有买手机的客户来买
}
}
}
interface PhoneShopper{
void sellPhone(Customer customer);
void notifyCustomer();
}
//kite是手机店长
class Kite implements PhoneShopper{
private List<Customer> customers=new ArrayList<>();//记录客户的电话名单
private boolean ifGoods=false;//是否有货
public boolean isIfGoods() {
return ifGoods;
}
public void setIfGoods(boolean ifGoods) {
this.ifGoods = ifGoods;
}
@Override
public void sellPhone(Customer customer) {//添加到名单中
customers.add(customer);
}
@Override
public void notifyCustomer() {//遍历名单通知客户来买
customers.forEach(customer -> customer.buyPhone());
}
}
//客户接口
interface Customer{
void buyPhone();
}
//客户1
class Jane implements Customer{
@Override
public void buyPhone() {
System.out.println("Jane要一部手机");
}
}
//客户2
class kellen implements Customer{
@Override
public void buyPhone() {
System.out.println("kellen要一部手机");
}
}
六,外观模式
定义:要求严格子系统内部与外部的通信必须通过一个统一的对象进行,外观者模式提供一个高层次的接口,使得子系统更易被使用。
缺点:不符合开闭原则,子系统修改必须修改外观模式的类,必须要修改相应的代码才能适应系统的变更
例子:我是一家小饭店的店主,因为刚刚开业所以采购菜品都是我一个人干的,采购菜品的话需要购买,菜品1,菜品2,菜品3。一段时间后饭店生意不错于是我决定招聘一个采购员来帮我采购。我只需要告诉他你去采购吧,他就把菜品1,菜品2,菜品3。采购回来不需要我去关心其中的细节。(采购员是外观者,它隐藏了买菜的细节)
代码实现:
package cn.itcast.test;
public class FacadePattern {
public static void main(String[] args) {
boolean if_buy = new Buyer().ifBuy();
if (if_buy){
System.out.println("开始做菜");
}
}
}
//菜品接口
interface Food{
boolean FoodInfo();
}
class Meat implements Food{
@Override
public boolean FoodInfo() {
System.out.println("猪肉买回来了");
return true;
}
}
class vegetable implements Food{
@Override
public boolean FoodInfo() {
System.out.println("蔬菜买回来了");
return true;
}
}
class Flour implements Food{
@Override
public boolean FoodInfo() {
System.out.println("面粉买回来了");
return true;
}
}
class Buyer {//采购员是外观者,它影藏了买菜的细节
Food food1=new Meat();
Food food2=new vegetable();
Food food3=new Flour();
boolean ifBuy(){
return food1.FoodInfo()&&food2.FoodInfo()&&food3.FoodInfo();
}
}
七,状态模式
定义:允许一个对象在其内部状态改变时改变它的行为,对象看起来似乎改变了它的类。其别名为状态对象,状态模式是一种对象行为模式
优点:封装了转化原则,并枚举了可能的状态,他将所有与某个状态有关的行为,放在了状态类中,并且可以方便地增加新的状态,还可以让多个环境对象共享一个对象,从而减少系统中对象的个数
缺点:使用状态模式会增加系统状态类,和状态对象的个数,如果使用不当会导致程序结构或代码的混乱
理解:我们根据状态的不同来执行不同的语句是通过if else语句来实现的。状态模式用来替换if else 语句,把对应的状态及其掉用的语句写成一个类,根据传入的对象的不同执行相应的语句
代码实现:
package cn.itcast.test.SingleTon;
public class StatePattern {
public static void main(String[] args) {
Context kite=new Context();
kite.changeState(new Happy());
kite.doWork();
System.out.println("-----------------");
kite.changeState(new Angry());
kite.doWork();
}
}
abstract class State{
public abstract void doWork();
}
class Happy extends State{//状态1
@Override
public void doWork() {
System.out.println("干活效率高");
}
}
class Sad extends State{//状态2
@Override
public void doWork() {
System.out.println("干活效率低");
}
}
class Angry extends State{//状态3
@Override
public void doWork() {
System.out.println("不干了");
}
}
class Context{//根据不同的状态干不同的东西
private State state;
public void changeState(State state){
this.state=state;
}
public void doWork(){
state.doWork();
}
}
八,策略模式和代理模式
策略模式:定义一组算法,将每个算法都封装起来,并且使得他们之间可以相互替换,策略模式让算法独立于它的客户而变化,也称为政策模式
代理模式:为其他对象提供一种代理以控制对这个对象的访问,把类的创建交给代理类创建,根据类加载器创建实现解耦
1,静态代理:例如(歌手的代理经纪人代理歌手除了唱歌外的其他事务,歌手负责唱歌就行)
package model;
/**
* 代理设计模式的功能:
* 对被代理对象进行扩展和增强
* 实现方式有:
* 1,代理类 和 被代理类 实现相同的接口,代理类中的方法对 被代理类中的方法增强,并调用它的方法
* 2,代理类 继承 被代理类 ,重写要增强的方法
*/
public class AgentModel {
public static void main(String[] args) {
Agent agent=new Agent(new MaleSinger("陈建江"));
agent.singing();
}
}
//唱歌接口
interface Singer {
public void singing();
}
//某个具体的歌星
class MaleSinger implements Singer {
private String name;
MaleSinger(String name){
this.name=name;
}
@Override
public void singing() {
System.out.println(name+"去唱歌");
}
}
//经纪人代理,负责歌手的事物。
class Agent implements Singer {
Singer singer;//代理人负责的 歌手
public Agent(Singer singer) {
this.singer = singer;
}
@Override
public void singing() {//对歌手唱歌的方法进行了增强,在不改变原有代码的情况下
//唱歌前的事物,,,,
System.out.println("代理人与合作方协商价钱,,,,,其它杂物");
//通知歌手去唱歌
singer.singing();
//唱歌后的事物,,,
System.out.println("结算费用");
}
}
2,jdk静态代理(继承相同的接口),通过调用jdk提供的方法来生成代理类(简化开发)。需要传入一些相关的参数就行
相关的类:InvocationHandler类用来调用处理程序,Proxy类用来代理
package model;
import javax.sound.midi.Soundbank;
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
import java.lang.reflect.Proxy;
/**
* 代理设计模式的功能:
* 对被代理对象进行扩展和增强
* 实现方式有:
* 1,代理类 和 被代理类 实现相同的接口,代理类中的方法对 被代理类中的方法增强,并调用它的方法
* 2,代理类 继承 被代理类 ,重写要增强的方法
*/
public class AgentModel {
public static void main(String[] args) {
MaleSinger maleSinger=new MaleSinger("建江");
//代理类,创建一个代理实例,传入三个参数:执行该方法的类加载器,被代理类实现的接口数组,继承回调处理器的代理对象
Singer singer = (Singer)Proxy.newProxyInstance(AgentModel.class.getClassLoader(), new Class[]{Singer.class}, new Agent(maleSinger));
singer.singing();//对方法进行增强
singer.service();
}
}
//唱歌接口
interface Singer {
void singing();
void service();
}
//某个具体的歌星
class MaleSinger implements Singer {
private String name;
MaleSinger(String name){
this.name=name;
}
@Override
public void singing() {
System.out.println(name+"去唱歌");
}
@Override
public void service() {
System.out.println(name+"提供配吃饭的服务");
}
}
//创建代理对象
class Agent implements InvocationHandler{
private Object object;//要代理的对象
public Agent(Object object) {
this.object = object;
}
@Override
//三个参数:传入的代理对象proxy,要增强的方法method,方法中的参数args
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
System.out.println("经纪人协商价钱");
Object invoke = method.invoke(object, args);
System.out.println("结算价钱");
return null;
}
}
3,
九,责任链模式
定义:是一种处理请求的模式,它让多个处理器都有机会处理该请求,直至其中某个请求处理成功为止,责任链模式把多个处理器窜成链,然后请求在链上传递。
例如:你是包租婆,有一天你去买房,你对销售员说你要买一栋房,销售员说它没有权限做这么大的生意于是把请求告诉了它的经理,经理也说它没有权限,于是把请求告诉了老板,最终由经理处理了你的请求。这种请求在链上传输的模式就是责任链模式。
代码实现:
package cn.itcast.test;
public class ChainPattern {
public static void main(String[] args) {
Handler saleMan=new SaleMan();
Handler manager=new Manager();
Handler boss=new Boss();
saleMan.SetNextHandler(manager);//销售员处理不了就给经理
manager.SetNextHandler(boss);//经理处理不了就给老板
//这样请求就在一个处理链上了
saleMan.process(5);
saleMan.process(12);
saleMan.process(299);
}
}
abstract class Handler{
protected Handler nextHandler;
public void SetNextHandler(Handler nextHandler){
this.nextHandler=nextHandler;
}
public abstract void process(Integer info);
}
class SaleMan extends Handler{
@Override
public void process(Integer info) {
if (info<10&&info>0){
System.out.println("销售员直接卖出房");
}
else{
nextHandler.process(info);
}
}
}
class Manager extends Handler{
@Override
public void process(Integer info) {
if (info<100&&info>=10){
System.out.println("销售经理直接卖出房");
}
else{
nextHandler.process(info);
}
}
}
class Boss extends Handler{
@Override
public void process(Integer info) {
System.out.println("老板卖出房");
}
}
十,模板方法模式
定义:定义一个操作中的算法框架,而将一些步骤延迟到子类中,使得子类可以不改变一个算法的结果,即可以重定义该算法的某些特定的步骤
例如:我制定一个炒菜的模板,先放油,放菜,炒菜,放调料,出锅
代码实现:把共有的方法实现在父类,具体实现的方法定位抽象方法给子类实现。整体上是一个模板
package cn.itcast.test;
public class TemPlatesPattern {
public static void main(String[] args) {
Cooking cooking=new CookingFood();
cooking.cook();
}
}
abstract class Cooking{
protected abstract void step1();
protected abstract void step2();
public void cook(){
System.out.println("开始炒菜");
step1();
step2();
System.out.println("出锅");
}
}
class CookingFood extends Cooking {
@Override
protected void step1() {
System.out.println("放油和菜");
}
@Override
protected void step2() {
System.out.println("炒菜放调料");
}
}
十一,享元模式
定义:运用共享技术有效地支持大量细粒度的对象
实例:共享单车
优点:通过共享池的技术可以极大减少内存中对象的数量,相同的对象在系统只保留一份可以大大节约系统的资源,该模式内外状态相互分离,外部状态不会影响内部状态。这使得享元对象可以在不同环境中被共享
缺点:复杂,外部状态要客户端传入会影响运行时间
代码实现:
package cn.itcast.test;
import java.util.HashSet;
import java.util.Set;
public class FlyWeightPattern {
public static void main(String[] args) {
Bike bike1=BikeFactory.getInstance().getBike();
bike1.ride("manA");
bike1.back();
Bike bike2=BikeFactory.getInstance().getBike();
bike2.ride("manB");
//bike2.back();
Bike bike3=BikeFactory.getInstance().getBike();
bike3.ride("manC");
bike3.back();
System.out.println(bike1==bike2);
System.out.println(bike3==bike2);
}
}
//共享单车抽象类,用来规范单车要实现的方法规范
abstract class Bike{
protected Integer state=0;//默认没有使用
//userName外部状态
abstract void ride(String userName);
abstract void back();
public Integer getState(){
return state;
}
}
//摩拜单车具体的实现类
class MoBike extends Bike{
private String bikeId;
public MoBike(String bikeId){
this.bikeId=bikeId;
}
@Override
void ride(String userName) {
state=1;
System.out.println(userName+"骑"+bikeId+"号自行车");
}
@Override
void back() {
state=0;
System.out.println(bikeId+"号自行车归还");
}
}
//使用工厂来创建自行车的对象(饿汉模式)
class BikeFactory{
private static BikeFactory instance=new BikeFactory();
private Set<Bike> pool=new HashSet<>();//创建一个自行车仓库
public static BikeFactory getInstance(){
return instance;
}
private BikeFactory(){//构造方法中,创建一些自行车放在仓库中
for(int i=0;i<2;i++){
pool.add(new MoBike(i+"号"));
}
}
public Bike getBike(){
for(Bike bike : pool){
if(bike.getState()==0)
return bike;
}
return null;
}
}
十二,原形模式:用原型实例指定创建对象的种类,并且通过拷贝这些原型创建新的对象。
作用:某些结构复杂的对象"的创建工作;由于需求的变化,这些对象经常面临着剧烈的变化,但是他们却拥有比较稳定一致的接口。
代码实现:
package cn.itcast.test;
public class BuilderTest {
public static void main(String[] args) {
House house=new House();
System.out.println("房子大小:"+house.getSize()+"房子的颜色"+house.getColor());
House colon= (House) house.clone();
System.out.println("房子大小:"+colon.getSize()+"房子的颜色"+colon.getColor());
System.out.println(colon.equals(house));//这两个对象不指向同一个地址
}
}
interface ProtoType{//克隆接口
Object clone();
}
class House implements ProtoType{
private String color;
private String size;
House(){
color="白色"+Math.random();
size="大型"+Math.random();
}
House(House house){
this.color=house.color;
this.size=house.size;
}
public String getSize() {
return size;
}
public String getColor() {
return color;
}
@Override
public Object clone() {//调用克隆方法实现对象的复制
return new House(this);
}
}
十三,