一、设计模式分类
设计模式根据工作的目的,分为创建型模式、结构型模式和行为型模式三类。
-
创建型模式:用于描述"怎样创建对象”,它的主要特点是"将对象的创建与使用分离”。
单例模式、工厂方法模式、抽象工厂模式、创建者模式、原型模式属于创建型模式。 -
结构型模式:用于描述如何将类或对象按某种布局组成更大的结构。
适配器模式、代理模式、装饰器模式、外观模式、桥接模式、组合模式、享元模式属于结构型模式。 -
行为型模式:用于描述类或对象之间怎样相互协作共同完成单个对象无法单独完成的任务,以及怎样分配职责。
策略模式、模板方法模式、观察者模式、迭代子模式、责任链模式、命令模式、备忘录模式、状态模式、访问者模式、中介者模式、解释器模式属于行为型模式。
其中最常用的七种设计模式是:单例模式、工厂方法模式、抽象工厂模式、代理模式、装饰器模式、观察者模式和责任链模式。
二、软件设计七大原则(OOP原则)
- 开闭原则:对扩展开放,对修改关闭。
- 里氏替换原则:不要破坏继承体系,子类重写方法功能发生改变,不应该影响父类方法的含义。
- 依赖倒置原则:要面向接口编程,不要面向实现编程。
- 单一职责原则:控制类的粒度大小、将对象解耦、提高其内聚性。
- 接口隔离原则:要为各个类建立它们需要的专用接口。
- 迪米特法则:一个类应该保持对其它对象最少的了解,降低耦合度。
- 合成复用原则:尽量先使用组合或者聚合等关联关系来实现,其次才考虑使用继承关系来实现。
实际上,七大原则的目的只有一个:降低对象之间的耦合,增加程序的可复用性、可扩展性和可维护性。
三、七大常用的设计模式
-
单例模式:一个类只有一个实例,且该类能自行创建这个实例的一种模式
①单例类只有一个实例对象
②该单例对象必须由单例类自行创建
③单例类对外提供一个访问该单例的全局访问点- 优点
单例模式可以保证内存里只有一个实例,减少了内存的开销。
可以避免对资源的多重占用。
单例模式设置全局访问点,可以优化和共享资源的访问。 - 缺点
单例模式一般没有接口,扩展困难。
单例模式的功能代码通常写在一个类中,如果功能设计不合理,则很容易违背单一职责原
- 优点
其中单例模式又可以分为饿汉式单例和懒汉时单例:
- 1.1 饿汉式单例:类一旦加载就创建一个单例,保证在调用getInstance方法之前单例已经存在,这种饿汉式单例会造成空间浪费。
public class HungryDemo {
private HungryDemo(){ }
private final static HungryDemo HUNGRY = new HungryDemo();
public static HungryDemo getInstance(){
return HUNGRY;
}
}
- 1.2 懒汉式单例:为了避免内存空间浪费,采用懒汉式单例,即用到该单例对象的时候再创建。
public class LazyDemo {
private LazyDemo(){};
public static LazyDemo lazyDemo;
public static LazyDemo getInstance(){
if (lazyDemo==null){
lazyDemo = new LazyDemo();
}
return lazyDemo;
}
}
这是最简单的懒汉式,但是,存在很大问题,单线程下这段代码没有问题,但是在多线程下有很大问题。
public class LazyDemo {
private LazyDemo() {
System.out.println(Thread.currentThread().getName() + " ");
}
public static LazyDemo lazyDemo;
public static LazyDemo getInstance() {
if (lazyDemo == null) {
lazyDemo = new LazyDemo();
}
return lazyDemo;
}
public static void main(String[] args) {
for (int i = 0; i < 10; i++) {
new Thread(() -> {
LazyDemo.getInstance();
}).start();
}
}
}
多次执行代码会发现结果都不一样,因此,并发情况下,这段代码是有问题的。我们需要进行两端检测,进行“加锁”:synchronized:
可以看到结果都是只有一个,按理来说是没有问题,实际上不是,上述标注行代码存在问题的,不是一个原子性操作。
-
工厂方法模式:实例化对象不是用new,用工厂方法替代。将选择实现类,创建对象统一管理和控制。从而将调用者跟我们的实现类解耦。
- 2.1 简单工厂模式:用来生产同一等级架构中的任意产品(对于增加新的产品,需要修改已有代码)
在简单工厂模式中,可以根据参数的不同返回不同类的实例。简单工厂模式专门定义一个类来负责创建其他类的实例。
接下来创建一个接口,两个实现类,一个工厂,一个测试类:
- 2.1 简单工厂模式:用来生产同一等级架构中的任意产品(对于增加新的产品,需要修改已有代码)
package JavaSE.设计模式.单例模式;
//创建手机接口
public interface Phone {
void name();
}
//创建华为实现类
class HuaWei implements Phone{
@Override
public void name() {
System.out.println("华为手机");
}
}
//创建小米实现类
class XiaoMi implements Phone{
@Override
public void name() {
System.out.println("小米手机");
}
}
//创建工厂
class PhoneFactory {
public static Phone getPhone(String phone){
if(phone.equals("华为")){
return new HuaWei();
}else if(phone.equals("小米")){
return new XiaoMi();
}else {
return null;
}
}
}
//测试类
class Consumer {
public static void main(String[] args) {
Phone p1= PhoneFactory.getPhone("华为");
Phone p2= PhoneFactory.getPhone("小米");
p1.name();
p2.name();
}
}
测试结果:
我们通过创建一个PhoneFactory类,成功的完成工厂的创建。我们在创建对象时,也就不需要直接创建对象,而是可以通过创建工厂,这样大大的降低了代码的耦合性。但是,静态工厂模式是不能添加数据的。比如说,我们想添加一个“Apple”手机类,你不直接修改PhoneFactory工厂代码,是不能实现的。所以,就有了第二种的工厂方法模式。
- 2.2 工厂方法模式:用来生产同一等级架构中的固定产品,一个工厂等级结构可以负责多个不同产品等级结构中的产品对象的创建 。(支持增加任意产品)
package JavaSE.设计模式.单例模式;
//创建手机接口
public interface Phone {
void name();
}
//创建魅族实现类
class MeiZu implements Phone{
@Override
public void name() {
System.out.println("魅族手机");
}
}
class Apple implements Phone{
@Override
public void name() {
System.out.println("苹果手机");
}
}
//创建手机工厂接口
interface PhoneFactory {
Phone getPhone();
}
//创建魅族工厂
class MeiZuFactory implements PhoneFactory{
@Override
public Phone getPhone() {
return new MeiZu();
}
}
class AppleFactory implements PhoneFactory{
@Override
public Phone getPhone() {
return new Apple();
}
}
//测试类
class Consumer {
public static void main(String[] args) {
Phone phone = new MeiZuFactory().getPhone();
phone.name();
Phone phone1 =new AppleFactory().getPhone();
phone1.name();
}
}
测试结果:
我们创建了手机工厂接口PhoneFactory,再创建华为工厂MeiZuFactory实现工厂,这样就可以通过MeiZuFactory创建对象。增加新的具体工厂和产品族很方便,比如说,我们想要增加小米,只需要创建一个小米工厂AppleFactory实现手机工厂接口PhoneFactory,合理的解决的简单工厂模式不能修改代码的缺点。但是,在现实使用中,简单工厂模式占绝大多数。
- 抽象工厂模式:抽象工厂模式提供了一个创建一系列相关或者相互依赖对象的接口,无需指定它们具体的类(围绕一个超级工厂创建其他工厂,该超级工厂称为工厂的工厂)
抽象工厂模式的主要角色:
(1)抽象工厂(Abstract Factory)提供了创建产品的接口,它包含多个创建产品的方法newProduct(),可以创建多个不同等级的产品。
(2)具体工厂(Concrete Factory)主要是实现抽象工厂中的多个抽象方法,完成具体产品的创建。
(3)抽象产品(Product)定义了产品的规范,描述了产品的主要特性和功能,抽象工厂模式有多个抽象产品。
(4)具体产品(ConcreteProduct)实现了抽象产品角色所定义的接口,由具体工厂来创建,它同具体工厂之间是多对一的关系。
package JavaSE.设计模式.工厂模式.抽象工厂模式;
//电脑接口
public interface Computer {
void open();
void shutDown();
}
//创建华为电脑对象
class HuaWeiComputer implements Computer{
@Override
public void open() {
System.out.println("华为---开机...");
}
@Override
public void shutDown() {
System.out.println("华为---关机...");
}
}
//手机接口
interface Phone {
void send();
void accept();
}
//创建华为手机对象
class HuaWeiPhone implements Phone{
@Override
public void send() {
System.out.println("华为手机---发送...");
}
@Override
public void accept() {
System.out.println("华为手机---接收...");
}
}
//抽象工厂
interface IProductFactory {
//生产手机
Phone phone();
//生产电脑
Computer computer();
}
//创建华为工厂
class HuaWeiFactory implements IProductFactory{
@Override
public Phone phone() {
return new HuaWeiPhone();
}
@Override
public Computer computer() {
return new HuaWeiComputer();
}
}
//测试类
class Test {
public static void main(String[] args) {
HuaWeiFactory huaWeiFactory = new HuaWeiFactory();
Phone phone = huaWeiFactory.phone();
phone.send();
phone.accept();
Computer computer = huaWeiFactory.computer();
computer.open();
computer.shutDown();
}
}
测试结果:
我们通过创建一个抽象工厂完成了对具体工厂的创建,只需要传入参数就可以实例化对象。具体产品在应用层的代码隔离,无需关心创建的细节将一个系列的产品统一到一起创建。将一系列产品规划到一起创建。但是,抽象工厂模式也存在着缺点。规定了所有可能被创建的产品集合,产品族中扩展新的产品困难,不可以增加产品,只能增加品牌。
-
代理模式:由于某些原因需要给某对象提供一个代理以控制对该对象的访问。这时,访问对象不适合或者不能直接引用目标对象,代理对象作为访问对象和目标对象之间的中介。
静态代理模式: 角色分析: 1、抽象角色:一般会使用接口或抽象类来解决 2、真实角色:被代理的角色 3、代理角色:代理真实角色,代理真实角色后我们会进行一些附属操作 4、访问角色:访问代理对象的人
package JavaSE.设计模式.代理模式;
//租房
public interface Rent {
void rent();
}
//房东
class Master implements Rent{
@Override
public void rent() {
System.out.println("房东 rent...");
}
}
//中介
class Agent implements Rent{
private Master master;
public Agent() {
}
public Agent(Master master) {
this.master = master;
}
@Override
public void rent() {
see();
master.rent();
fare();
}
//看房
public void see(){
System.out.println("看房...");
}
//收费
public void fare(){
System.out.println("收费...");
}
}
//测试类
class Test {
public static void main(String[] args) {
Master master = new Master();
//进行代理
Agent agent = new Agent(master);
//不需要通过对象,直接通过代理完成响应业务
agent.rent();
}
}
测试结果:
可以从上述代码看出,我们通过创建中介这一代理完成了租房,并且还有看房、收费的附属操作。我们不需要使用房东对象,通过使用代理中介就可以完成操作。
- 代理模式优点:可以使真实角色的操作更加纯粹!不用去关注一些公共的业务,公共也就可以交给代理角色,实现了业务的分工,公共业务发生扩展的时候,方便集中管理!
- 代理模式缺点:一个真实角色就会产生一个代理角色;代码量会翻倍开发效率会变低,也许,这样无法理解到代理模式的好处。举个例子也许能更好理解,比如说我们想要在原有固定功能上新增业务,按照开闭原则我们是不能对原有代码进行修改的。但是我们可以通过代理模式,增加代理,在实现原有功能的情况下写入新的功能,创建对象时也就可以使用代理,完成操作。
动态代理模式:虽然静态代理模式可以很好的解决开闭原则,但是每有一个真实角色就会产生一个代理,代码量翻倍过于臃肿,开发效率较低。因此,我们就使用动态代理模式进行设计。
(1)创建接口
package JavaSE.设计模式.代理模式.动态代理;
//接口
public interface IUserService {
void add();
void delete();
void update();
void query();
}
(2)创建接口实现类
//实现类
public class UserServiceImpl implements IUserService {
@Override
public void add() {
System.out.println("添加...");
}
@Override
public void delete() {
System.out.println("删除...");
}
@Override
public void update() {
System.out.println("更新...");
}
@Override
public void query() {
System.out.println("查询...");
}
}
(3)创建代理类模板
package JavaSE.设计模式.代理模式.动态代理;
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
import java.lang.reflect.Proxy;
//自动生成动态代理类模板
public class ProxyInvocationHandler implements InvocationHandler {
//被代理接口
private Object target;
public void setTarget(Object target) {
this.target = target;
}
//得到代理类
public Object getProxy() {
return Proxy.newProxyInstance(getClass().getClassLoader(), target.getClass().getInterfaces(), this);
}
public void log(String s) {
System.out.println("[debug]:" + s);
}
//调用方法
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
log(method.getName());
Object result = method.invoke(target, args);
return result;
}
}
//测试类
class Test {
public static void main(String[] args) {
UserServiceImpl userService = new UserServiceImpl();
ProxyInvocationHandler handler = new ProxyInvocationHandler();
//设置代理对象
handler.setTarget(userService);
//生成代理类
IUserService proxy = (IUserService) handler.getProxy();
proxy.add();
proxy.query();
}
}
测试结果:
通过测试我们可以顺利的使用动态代理模式完成一系列操作,当我们想要添加附属操作时,我们只需要在模板中进行添加。
- 优点:
①可以使真实角色的操作更加纯粹!不用去关注一些公共的业务。
②公共也就可以交给代理角色!实现了业务的分工。
③公共业务发生扩展的时候,方便集中管理。
④一个动态代理类代理的是一个接口,一般就是对应的一类业务。
⑤一个动态代理类可以代理多个类,只要是实现了同一个接口即可!
-
装饰者模式:动态的将新功能附加到对象上。在对象功能的拓展方面,比继承更有弹性。同时装饰者模式也体现了开闭原则。
角色分析: 1、抽象构件(Component)角色:定义一个抽象接口以规范准备接收附加责任的对象。 2、具体构件(ConcreteComponent)角色:实现抽象构件,通过装饰角色为其添加一些职责。 3、抽象装饰(Decorator)角色:继承抽象构件,并包含具体构件的实例,可以通过其子类扩展具体构件的功能。 4、具体装饰(ConcreteDecorator)角色:实现抽象装饰的相关方法,并给具体构件对象添加附加的责任。
- 定义抽象类
package JavaSE.设计模式.装饰者模式;
//定义抽象类
public abstract class Drink {
public abstract double cost();
}
- 定义两个抽象类的实现
public class Juice extends Drink{
@Override
public double cost() {
System.out.println("Juice cost:"+15.0);
return 15.0;
}
}
public class Milk extends Drink{
@Override
public double cost() {
System.out.println("wine cost :"+28.0);
return 28.0;
}
}
- 定义装饰抽象类
public class Chocolate extends Decorator{
private final static double COST = 4;
private Drink drink;
public Chocolate(Drink drink) {
this.drink = drink;
}
@Override
public double cost() {
System.out.println("chocolate:"+4);
return COST+drink.cost();
}
}
public class Sugar extends Decorator {
private final static double COST =3.0;
private Drink drink;
public Sugar(Drink drink){
this.drink =drink;
}
@Override
public double cost() {
System.out.println("sugar :"+3.0);
return COST+drink.cost();
}
}
- 测试
public class Test {
public static void main(String[] args) {
Drink milk =new Milk();
milk =new Chocolate(milk);
milk =new Sugar(milk);
System.out.println("共计花费:"+milk.cost());
}
}
- 测试结果
可以看到非常简单的就能够完成具体构件和具体装饰的组合。也可以看到结构也非常简洁明,具体构件在自己的package中,具体装饰也在自己的package中。我们不管是想要增加具体构件还是具体配饰,都可以在各自的package中添加。对已有的代码不需要进行任何操作,就算新加的有bug也不会影响原有代码的操作。
-
观察者模式:对象间的一种一对多依赖关系,使得每当一个对象状态发生改变时,其相关依赖对象皆得到通知并被自动更新。这种模式有时又称作发布-订阅模式、模型-视图模式,它是对象行为型模式。。
观察者模式特点: 1、降低了目标与观察者之间的耦合关系,两者之间是抽象耦合关系。符合依赖倒置原则。 2、目标与观察者之间建立了一套触发机制。
- 定义观察者接口
package JavaSE.设计模式.观察者模式;
//定义观察者接口
public interface Observer {
void response();
}
- 具体化观察者
//具体观察者1
public class Observer1 implements Observer{
@Override
public void response() {
System.out.println("观察者1..response");
}
}
//具体观察者2
public class Observer2 implements Observer{
@Override
public void response() {
System.out.println("观察者2..response");
}
}
- 抽象化目标
import java.util.ArrayList;
import java.util.List;
//抽象目标
public abstract class Subject {
//创建观察者集合
protected List<Observer> list =new ArrayList<>();
//增加观察者
public void add(Observer observer){
list.add(observer);
}
//删除观察者
public void delete(Observer observer){
list.remove(observer);
}
//通知观察者方法
public abstract void notifyObserver();
//具体化目标
public class Subject1 extends Subject{
@Override
public void notifyObserver() {
for (Observer observer : list) {
observer.response();
}
}
}
- 测试
public class Test {
public static void main(String[] args) {
Subject subject =new Subject1();
Observer obs1 =new Observer1();
Observer obs2 = new Observer2();
subject.add(obs1);
subject.add(obs2);
subject.notifyObserver();
}
}
通过测试我们可以看到,我们不需要建立太多观察者和具体目标之间的联系,大大降低了目标与观察者之间的耦合关系。并且结构也十分简单明了,观察者和目标分别在各自的package包下。当我们想要添加观察者时,只需要在观察者包下进行添加,实现Observer接口就可以了。
-
责任链模式:一种处理请求的模式,它让多个处理器都有机会处理该诘求,直到其中某个处理成功为止。责任链模式把多个处理器串成链,然后让请求在链上传递。
-
7.1 责任链模式的主要角色
抽象处理者(Handler)角色:定义一个处理请求的接口,包含抽象处理方法和一个后继连接。
具体处理者(Concrete Handler)角色:实现抽象处理者的处理方法,判断能否处理本次请求,如果可以处理请求则处理,否则将该请求转给它的后继者。
客户类(Client)角色:创建处理链,并向链头的具体处理者对象提交请求,它不关心处理细节和请求的传递过程。 -
7.2 责任链模式优点
(1)降低了对象之间的耦合度。处理者不需要知道客户的任何信息,客户也不要知道处理者是如何实现方法的。
(2)提高了系统的灵活性。当我们想要新增处理器到整个链条中时,所付出的代价是非常小的 -
7.3 责任链模式缺点
(1)降低了系统的性能。对比较长的职责链,请求的处理可能涉及多个处理对象
(2)不能保证每个请求一定被处理。由于一个请求没有明确的接收者,所以不能保证它一定会被处理,该请求可能一直传到链的末端都得不到处理。
-
package JavaSE.设计模式.责任链模式;
//抽象处理者
public abstract class Handler {
private Handler handler;
//get/set 方法
public Handler getHandler() {
return handler;
}
public void setHandler(Handler handler) {
this.handler = handler;
}
//处理请求
public abstract void handleRequest(int num);
}
--------------------------------------------------
//具体的处理者1
public class Hanlder1 extends Handler{
@Override
public void handleRequest(int num) {
if (num <10){
System.out.println("Handler1 完成处理");
}else {
if (getHandler() !=null){
getHandler().handleRequest(num);
}else {
System.out.println("没有处理者进行处理!");
}
}
}
}
--------------------------------------------------
//具体的处理者2
public class Hanlder2 extends Handler{
@Override
public void handleRequest(int num) {
if (num >10 && num<20){
System.out.println("Handler2 完成处理");
}else {
if (getHandler() !=null){
getHandler().handleRequest(num);
}else {
System.out.println("没有处理者进行处理!");
}
}
}
}
- 测试
public class Test {
public static void main(String[] args) {
Handler handler1 =new Hanlder1();
Handler handler2 =new Hanlder2();
handler1.setHandler(handler2);
handler1.handleRequest(5);
handler1.handleRequest(15);
handler1.handleRequest(25);
}
}
通过测试结果我们看到,5交给了Handler1处理,15交给了Handler2处理,而25则没有处理者处理。请求者根本不需要参与处理,只需要提交数据就可以完成功能的处理,完全不需要管是哪个处理者进行处理的。当我们想要继续添加处理者时,这只需要再次添加就可以了,也不会对之前的代码造成影响。