Java设计模式和原则详解

一、简介

  • 23种设计模式,7大设计原则
设计模式说明
单例模式
观察者模式
装饰模式
适配器模式
工厂方法模式
代理模式
建造者模式
桥接模式
原型模式
抽象工厂模式
外观模式
享元模式
组合模式
策略模式
命令模式
责任链模式
状态模式
中介者模式
迭代器模式
访问者模式
备忘录模式
解释器模式
设计原则说明
开闭原则
里氏替换原则
依赖倒置原则
单一职责原则
接口隔离原则
迪米特原则
合成复用原则

二、设计模式

1、单例模式
  • 一个类只有一个实例,且该类能自行创建这个实例的一种模式
  • 适用场景
1、某类只要求生成一个对象时。如一个班的班长,每个人的身份证号
2、在对象需要被共享的场合。由于单例模式只允许创建一个对象,共享该对象可以节省内存,并加快对象访问速度。
   如Web中的配置对象,数据库的连接池等
3、当某类需要频繁实例化,而创建的对象又频繁被销毁时。如多线程的线程池,网络连接池等
(1)懒汉式实现
public class LazySingleton {

    private static volatile LazySingleton instance=null;     //保证instance在所有线程中同步
    private LazySingleton(){}

    public static synchronized LazySingleton getInstance(){ //方法前加同步
        if(instance==null){
            instance=new LazySingleton();
        }
        return instance;
    }
}

编写的多线程程序,不要删除代码关键字volatilesynchronized,否则将存在线程非安全问题;
如果不删除这两个关键字就能保证线程安全,但每次访问时都要同步,会影响性能,且消耗更多的资源,这是懒汉式单例的缺点
(2)饿汉式实现
public class HungrySingleton {
    private static final HungrySingleton instance=new HungrySingleton();
    private HungrySingleton(){}
    
    public static HungrySingleton getInstance(){
        return instance;
    }
}

在类创建的同时就已经创建好一个静态的对象供系统使用,以后不再改变,所以是线程安全的
2、观察者模式
  • 多个对象间存在一对多的依赖关系,当一个对象的状态发生改变时,所有依赖它的对象都得到通知并被自动更新
  • 也称发布-订阅模式,模型-视图模式
(1)实现
美美发出“大家来玩游戏”的消息,消息被小明和李雷接受到,立马跑到美美家门口
在这里,美美是被观察者,小明和李雷是观察者,被观察者发出一条信息,然后观察者们进行相应的处理
public interface Person {
    void getMessage(String str);
}

//小明
public class XiaoMing implements Person{
    @Override
    public void getMessage(String str) {
        System.out.println("小明接到美美的通知:"+str);
    }
}

//李雷
public class LiLei implements Person{
    @Override
    public void getMessage(String str) {
        System.out.println("李雷接到美美的通知:"+str);
    }
}

//美美--发出通知
public class MeiMei {
    List<Person> list=new ArrayList<Person>();

    public MeiMei(){}

    public void addPerson(Person p){
        list.add(p);
    }

    //遍历List,发通知给所有人
    public void notifyPerson(){
        for(Person p:list){
            p.getMessage("大家过来玩游戏噢!");
        }
    }
}

//测试
public class Test {
    public static void main(String[] args){
        MeiMei meiMei=new MeiMei();

        XiaoMing xiaoMing=new XiaoMing();
        LiLei liLei=new LiLei();

        meiMei.addPerson(xiaoMing);	//小明和李雷都在美美那里注册了一下
        meiMei.addPerson(liLei);

        meiMei.notifyPerson();	//美美向小明、李雷发出通知

    }
}
3、装饰模式
  • 不改变现有对象结构情况下,动态给对象增加一些职责(额外功能)
通常情况,扩展一个类的功能会使用继承方式实现,但继承具有静态特征,耦合度高,且随扩展功能增多,子类会很膨胀;
如果使用组合关系类创建一个包装对象(即装饰对象)类包裹真实对象,且在保持真实对象的类结构不变的前提下,为其提供额外的功能,
这就是装饰者模式的目标
(1)实现
想吃三明治,首先有一根香肠。在此基础上加一下配料,抹上奶油,再放一点蔬菜,最后用两片面包夹一下
public class Food {
    private String name;

    public Food(){}
    public Food(String name){
        this.name=name;
    }

    public String makeFood(){
        return name;
    }
}

//奶油
public class Cream extends Food{
    private Food food;

    public Cream(Food f){
        this.food=f;
    }

    @Override
    public String makeFood(){
        return food.makeFood()+"奶油+";
    }
}

//蔬菜
public class Vegetable extends Food{
    private Food food;

    public Vegetable(Food f){
        this.food=f;
    }

    @Override
    public String makeFood(){
        return food.makeFood()+"蔬菜+";
    }
}

//面包
public class Bread extends Food {
    private Food food;

    public Bread(Food f){
        this.food=f;
    }

    @Override
    public String makeFood(){
        return food.makeFood()+"面包";
    }
}

//测试
public class Test {
    public static void main(String[] args){
        Food food=new Bread(new Vegetable(new Cream(new Food("香肠+"))));
        System.out.println(food.makeFood());
    }
}

从里往外,最里面new了一个香肠,香肠外包裹奶油,奶油外加蔬菜,最外面是面包;
设计模式跟现实生活神似
4、适配器模式
将一个类的接口转换成客户希望的另一个接口,使得原本由于不兼容而不能一起工作的那些类能一起工作
例如:
	1.有个电器插头是3脚的,而现有插座是2孔,所以需要一个插头转换器,即适配器
  • 涉及角色:
  1. :需要被适配的对象或类型,相当于插头
  2. 目标:期待得到的目标,相当于插座
  3. 适配器:连接目标和源的中间对象,相当于插头转换器
(1)类适配器模式
------------继承的方式
//源
public class Source {
    public void eat(){
        System.out.println("吃喝");
    }
}

//目标
public interface Target {
    public void eat();
    public void sleep();
}

//适配器
public class Adapter extends Source implements Target{
    @Override
    public void sleep(){
        System.out.println("睡觉");
    }
}

//测试
public class Test {
    public static void main(String[] args){
        Adapter adapter=new Adapter();
        adapter.eat();
        adapter.sleep();
    }
}
(2)对象适配器模式
------------组合的方式
//源
public class Source {
    public void eat(){
        System.out.println("吃喝");
    }
}

//目标
public interface Target {
    public void eat();
    public void sleep();
}

//适配器
public class Adapter implements Target{
    private Source source;

    public Adapter(Source source){
        this.source=source;
    }

    @Override
    public void eat(){
        source.eat();
    }

    @Override
    public void sleep(){
        System.out.println("睡觉");
    }
}

//测试
public class Test {
    public static void main(String[] args){
        Adapter adapter=new Adapter(new Source());
        adapter.eat();
        adapter.sleep();
    }
}
(3)接口适配器模式
为一个接口提供缺省实现,这样子类可以从这个缺省实现进行扩展,而不必从原有接口进行扩展
JFrame jf=new JFrame();
jf.addKeyListener(new KeyListener() {
     @Override
     public void keyTyped(KeyEvent e) {

     }

     @Override
     public void keyPressed(KeyEvent e) {
          System.out.println("按键事件");
     }

     @Override
     public void keyReleased(KeyEvent e) {

     }
});

KeyListener是一个键盘监听接口,上面我们只用到其中一个keyPressed方法,但必须把接口方法全部实现一遍,不科学。
因此我们可以引入一个默认适配器,它把接口的方法都实现一遍,使用时继承这个适配器,只把所需的方法实现就行。
java中为java.awt.KeyListener已提供了一个适配器java.awt.KeyAdapter,修改后如下:
jf.addKeyListener(new KeyAdapter() {
     @Override
     public void keyPressed(KeyEvent e) {
          System.out.println("按键事件");
     }
});
优点:
	复用性:系统需要使用现有的类,而此类的接口不符合系统的需要。那么通过适配器模式就可以让这些功能得到更好的复用。
	扩展性:在实现适配器功能的时候,可以扩展自己源的行为(增加方法),从而自然地扩展系统的功能。
缺点:
	系统紊乱:滥用适配器,会让系统变得非常零乱
	如明明看到调用的是A接口,其实内部被适配成了B接口的实现,一个系统如果太多出现这种情况,无异于一场灾难。因此如果不是很有必要,可以不使用适配器,而是直接对系统进行重构。
5、工厂方法模式
定义一个用于创建对象的接口,让子类决定实例化哪一个类。工厂方法使一个类的实例化延迟到其子类
优点:
	1、用户只需知道具体工厂即可得到产品,无需了解具体产品生产过程
	2、增加新产品只需添加具体产品和对应具体工厂,无需修改原工厂,满足开闭原则
缺点:
	1、每增加一个产品需要加具体产品和对应具体工厂,增加了系统复杂度
  • 涉及角色:
  1. 抽象工厂:提供了创建产品的接口,调用者通过它访问具体工厂的工厂方法new Product()来创建产品
  2. 具体工厂:实现抽象工厂的抽象方法,完成具体产品的创建
  3. 抽象产品:定义产品规范,描述产品主要特性和功能
  4. 具体产品:实现抽象产品接口,由具体工厂吃Aug你家,与具体工厂一一对应
(1)普通工厂模式
//发送接口
public interface Sender {
    public void send();
}

//发送邮件
public class MailSender implements Sender {
    @Override
    public void send(){
        System.out.println("发送邮件");
    }
}

//发送短信
public class SmsSender implements Sender {
    @Override
    public void send(){
        System.out.println("发送短信");
    }
}

//测试
public class Test {
    public static void main(String[] args){
        Sender s1=new MailSender();     //发送邮件
        s1.send();
        Sender s2=new SmsSender();      //发送短信
        s2.send();
    }
}
(2)多个工厂模式
//抽象产品接口
public interface Product {
    public void show();
}
//可乐产品
public class ColaProduct implements Product{
    @Override
    public void show(){
        System.out.println("我是可乐!");
    }
}
//雪碧产品
public class SpriteProduct implements Product{
    @Override
    public void show() {
        System.out.println("我是雪碧!");
    }
}
//抽象工厂接口
public interface Factory {
    public Product startProduce();
}
//可乐工厂
public class ColaFactory implements Factory{
    @Override
    public Product startProduce() {
        System.out.println("开始生产可乐!");
        return new ColaProduct();
    }
}
//雪碧工厂
public class SpriteFactory implements Factory{
    @Override
    public Product startProduce() {
        System.out.println("开始生产雪碧!");
        return new SpriteProduct();
    }
}
//测试
public class Test {
    public static void main(String[] args){
        //生产可乐
        Factory colaFactory=new ColaFactory();
        Product colaProduct=colaFactory.startProduce();
        colaProduct.show();

        //生产雪碧
        Factory spriteFactory=new SpriteFactory();
        Product spriteProduct=spriteFactory.startProduce();
        spriteProduct.show();
    }
}
6、代理模式
由于某些原因需要给某对象提供一个代理以控制对该对象的访问
这时,访问对象不适合或者不能直接引用目标对象,代理对象作为访问对象和目标对象之间的中介
有些情况下,一个客户不能或者不想直接访问另一个对象,这时需要找一个中介帮忙完成某项任务,这个中介就是代理对象。
例如,购买火车票不一定要去火车站买,可以通过 12306 网站或者去火车票代售点买。又如找女朋友、找保姆、找工作等都可以通过找中介完成
(1)实现
//主题接口
public interface Subject {
    void request();
}
//具体主题
public class RealSubject implements Subject{

    @Override
    public void request(){
        System.out.println("访问真实主题!");
    }
}
//代理主题
public class Proxy implements Subject{
    private RealSubject realSubject;

    @Override
    public void request(){
        if(realSubject==null){
            realSubject=new RealSubject();
        }
        preRequest();
        realSubject.request();
        postRequest();
    }

    public void preRequest(){
        System.out.println("预处理......");
    }

    public void postRequest(){
        System.out.println("处理后......");
    }
}
//测试
public class Test {
    public static void main(String[] args){
        Proxy proxy=new Proxy();
        proxy.request();
    }
}
7、建造者模式
将一个复杂对象的构造与它的表示分离,使同样的构建过程可以创建不同的表示,这样的设计模式被称为建造者模式
有时需要创建一个复杂的对象,这个复杂对象通常由多个子部件按一定的步骤组合而成。
例如计算机是由 OPU、主板、内存、硬盘、显卡、机箱、显示器、键盘、鼠标等部件组装而成的,采购员不可能自己去组装计算机,而是将计算机的配置要求告诉计算机销售公司,计算机销售公司安排技术人员去组装计算机,然后再交给要买计算机的采购员。

生活中这样的例子很多,如游戏中的不同角色,其性别、个性、能力、脸型、体型、服装、发型等特性都有所差异;
还有汽车中的方向盘、发动机、车架、轮胎等部件也多种多样;
每封电子邮件的发件人、收件人、主题、内容、附件等内容也各不相同。
//机器人
public class Robot {
    private String head;
    private String body;
    private String hand;
    private String foot;

    public String getHead() {
        return head;
    }

    public void setHead(String head) {
        this.head = head;
    }

    public String getBody() {
        return body;
    }

    public void setBody(String body) {
        this.body = body;
    }

    public String getHand() {
        return hand;
    }

    public void setHand(String hand) {
        this.hand = hand;
    }

    public String getFoot() {
        return foot;
    }

    public void setFoot(String foot) {
        this.foot = foot;
    }
}

//建造机器人接口
public interface IBuilderRobot {
    public void buildHead();
    public void buildBody();
    public void buildHand();
    public void buildFoot();

    public Robot buildRobot();
}
//建造跳舞机器人
public class DanceRobotBuilder implements IBuilderRobot{
    Robot robot;

    public DanceRobotBuilder(){
        robot=new Robot();
    }

    @Override
    public void buildHead() {
        robot.setHead("造头");
    }

    @Override
    public void buildBody() {
        robot.setBody("造身体");
    }

    @Override
    public void buildHand() {
        robot.setHand("造手");
    }

    @Override
    public void buildFoot() {
        robot.setFoot("造脚");
    }

    @Override
    public Robot buildRobot(){
        return robot;
    }
}

//指挥官
public class Director {
    public Robot createRobotByDirector(IBuilderRobot ibr){
        ibr.buildHead();
        ibr.buildBody();
        ibr.buildHand();
        ibr.buildFoot();
        return ibr.buildRobot();
    }
}

//测试
public class Test {
    public static void main(String[] args){
        Director dr=new Director();
        Robot rb=dr.createRobotByDirector(new DanceRobotBuilder());

        System.out.println(rb.getBody());
    }
}
8、桥接模式
  • 将抽象与实现分离,使它们可以独立变化
优点:
	1、由于抽象与实现分离,所以扩展能力强
	2、其实现细节对客户透明

三、设计原则

1、开闭原则
  • 当应用的需求改变时,在不修改软件实体的源代码或者二进制代码的前提下,可以扩展模块的功能,使其满足新的需求
通过“抽象约束,封装变化”来实现开闭原则
即通过接口和抽象类为软件定义一个相对稳定的抽象层,而将相同的可变因素封装到相同的具体实现类中
软件中稳定的用抽象或接口约束,易变的用实现类扩展

示例,定义职业人的行为:
	固定的:吃饭 睡觉 
	易变的:开车 卖东西
开闭原则的意思是:对扩展开放,对修改关闭。
在程序需要进行拓展的时候,不能去修改原有的代码,实现一个热插拔的效果。简言之,是为了使程序的扩展性好,易于维护和升级。
(1)实现
public interface Person {
    public void eat();
    public void sleep();
}

//司机
public class Driver implements Person{
    private String name;
    public Driver(String name){
        this.name=name;
    }

    @Override
    public void eat() {
        System.out.println("在吃饭");
    }

    @Override
    public void sleep() {
        System.out.println("在睡觉");
    }

    //独有
    public void drive(){
        System.out.println("在开车");
    }
}

//售货员
public class Seller implements Person{
    private String name;
    public Seller(String name){
        this.name=name;
    }

    @Override
    public void eat() {
        System.out.println("在吃饭");
    }

    @Override
    public void sleep() {
        System.out.println("在睡觉");
    }

    //独有
    public void sell(){
        System.out.println("在卖货");
    }
}

//测试
public class Test {
    public static void main(String[] args){
        Driver driver=new Driver("司机");
        driver.eat();
        driver.sleep();
        driver.drive();
        Seller seller=new Seller("售货员");
        seller.eat();
        seller.sleep();
        seller.sell();
    }
}
2、里氏替换原则
  • 继承必须确保超类所拥有的性质在子类中仍然成立
里氏代换原则是面向对象设计的基本原则之一。
里氏代换原则中说,任何基类可以出现的地方,子类一定可以出现。
LSP 是继承复用的基石,只有当派生类可以替换掉基类,且软件单位的功能不受到影响时,基类才能真正被复用,而派生类也能够在基类的基础上增加新的行为。
里氏代换原则是对开闭原则的补充。
实现开闭原则的关键步骤就是抽象化,而基类与子类的继承关系就是抽象化的具体实现,所以里氏代换原则是对实现抽象化的具体步骤的规范。
(1)实现
鸟一般都会飞行,如燕子的飞行速度每小时120千米,几维鸟由于翅膀退化无法飞行。
假如要设计一个实例,计算这两种鸟飞行48000千米要花费的时间
//鸟类
public class Bird {
    double flySpeed;
    public void setFlySpeed(double flySpeed){
        this.flySpeed=flySpeed;
    }
    public double getCostTime(double distince){
        return distince/flySpeed;
    }
}
//燕子
public class Swallow extends Bird{
}
//几维鸟
public class Kiwi extends Bird{
    @Override
    public void setFlySpeed(double flySpeed){
        flySpeed=0;
    }
}
//测试
public class Test {
    public static void main(String[] args){
        Bird swallow=new Swallow();
        swallow.setFlySpeed(120);
        System.out.println("燕子飞行耗时:"+swallow.getCostTime(48000));
        Bird kiwi=new Kiwi();
        kiwi.setFlySpeed(0);
        System.out.println("几维鸟飞行耗时:"+kiwi.getCostTime(48000));
    }
}

结果:
	燕子飞行耗时:400.0
	几维鸟飞行耗时:Infinity
因为几维鸟重写了鸟类的setSpeed方法,违背了里氏替换原则
正确的做法是,取消几维鸟原继承关系,定义鸟和几维鸟更一般的父类,比如动物类
动物类都有奔跑的能力,几维鸟虽然飞行速度为0,但奔跑速度不为0,可以算出消耗时间
//动物
public class Animal {
    double runSpeed;

    public void setRunSpeed(double runSpeed){
        this.runSpeed=runSpeed;
    }

    public double getCostTime(double distince){
        return distince/runSpeed;
    }
}
//几维鸟
public class Kiwi extends Animal{
}
//测试
Animal kiwi=new Kiwi();
kiwi.setRunSpeed(16);
System.out.println("几维鸟飞行耗时:"+kiwi.getCostTime(48000));
3、接口隔离原则
  • 这个原则是开闭原则的基础,具体内容:针对接口编程,依赖于抽象而不依赖于具体
4、依赖倒转原则
这个原则的意思是:使用多个隔离的接口,比使用单个接口要好。它还有另外一个意思是:降低类之间的耦合度。由此可见,其实设计模式就是从大型软件架构出发、便于升级和维护的软件设计思想,它强调降低依赖,降低耦合。
5、迪米特法则
又称最少知道原则
最少知道原则是指:一个实体应当尽量少地与其他实体之间发生相互作用,使得系统功能模块相对独立。
6、合成复用原则
合成复用原则是指:尽量使用合成/聚合的方式,而不是使用继承
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值