OO(面向对象)的设计模式

本文深入探讨了面向对象设计模式,包括策略模式、观察者模式、装饰者模式、工厂模式等14种常见模式,阐述了它们的定义、应用场景及核心思想。设计模式是面向对象编程中的经验总结,能帮助开发者设计出更弹性、可复用和可维护的系统。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

为何学习设计模式:

      知道OO(面向对象)的基础概念(封装、抽象、继承、多态),不等于能够自动设计出弹性的、可复用的、可维护的系统。设计模式是人们在不断运用OO所总结出来的经验。

     设计模式比库的等级更高。设计模式告诉我们如何组织类和对象以解决某种问题。

设计模式的三原则:

1. 面向接口编程,而非实现;

2. 多用组合,少用继承;

3. 高内聚、低耦合;

一、策略设计模式的定义

策略设计模式:定义了算法族,分别封装起来,让他们之间可以互相替换,此模式让算法的变化独立于使用算法的客户。

案例:Duck

二、观察者模式

观察者模式:定义了对象之间的一对多依赖,这样一来,当一个对象改变状态时,它的所有依赖者都会收到通知并自动更新。

三、装饰者模式

装饰者模式:动态地将责任附加到对象上。若要扩展功能,装饰者提供了比继承更有弹性的替代方案。

关键点:装饰者可以在所委托被装饰者的行为之前与/或之后,加上自己的行为,以达到特定的目的。

四、工厂模式

所有的工厂模式都是用来封装对象的创建。

4.1 简单工厂模式

简单工厂其实不是一个设计模式,反而比较像是一种编程习惯。

/**
 * 简单工厂
 * @Date 2019-06-02
 * @Author lifei
 */
public class SimplePizzaFactory {

    /**
     * 创建pizza
     * 简单工厂, 这样做,制作比萨的代码绑定到PizzaStra中,没有弹性
     * @param type
     * @return
     */
    public Pizza createPizza(String type){
        Pizza pizza = null;
        if (type.equals("cheese")){
            pizza = new CheesePizza();
        }else if (type.equals("greek")){
            pizza = new GreekPizza();
        }else if (type.equals("pepperoni")){
            pizza = new PepperoniPizza();
        }
        return pizza;
    }
}

/**
 * Factory pattern
 * @Date 2019-06-02
 * @Author lifei
 */
public class PizzaStore {

    SimplePizzaFactory factory;

    public PizzaStore(SimplePizzaFactory simplePizzaFactory) {
        this.factory = simplePizzaFactory;
    }
    /**
     * 制作pizza
     * @return
     */
    public Pizza orderPizza(String type){
        Pizza pizza =null;
        // 使用简单工厂创建pizza
        /**
         * 简单工厂其实不是一种设计模式, 反而比较像是一种编程习惯。
         */
        pizza = factory.createPizza(type);

        pizza.prepare();
        pizza.bake();
        pizza.cut();
        pizza.box();


        return pizza;
    }
}

4.2 工厂方法模式

工厂方法模式(Factory Method Pattern): 定义了一个创建对象的接口,但由子类决定要实例化的类是哪一个。工厂方法让类把实例化推迟到子类。

通过子类决定该创建的对象是什么,来达到对象创建的过程封装的目的。

4.3 抽象工厂模式

抽象工厂模式:提供一个接口,用于创建相关或依赖对象的家族,而不需要明确指定具体类。

五、单件模式

单件模式:确保一个类只有一个实例,并提供一个全局访问点。

将构造函数私有化,然后提供一个public static修饰的方法(类方法)获取实例对象。

三种方式:

方法一:同步getInstance() 方法既简单又有效。同步一个方法可能造成程序执行效率下降100倍,因此,如果将getInstance()的程序使用在频繁运行的地方,就需要重新考虑了。

private static Singleton uniqueInstance;
public static synchronized Singleton getInstance(){
    if(uniqueInstance == null){
       uniqueInstance = new Singleton();
    }
    return uniqueInstance;
}

方法二:使用“急切(eagerly)”创建实例,而不用延迟实例的做法

这种做法,当应用程序总是创建并使用单件实例,或者在创建和运行时方面的负担不太繁重时使用。

jvm保证在任何线程访问uniqueInstance静态变量之前,一定先创建此实例。

public class Singleton{
  // 这段代码保证了线程安全
  private static Singleton uniqueInstance = new Singleton();

  private Singleton(){}

  public static Singleton getInstance(){
     return uniqueInstance;
  }
}

方法三:使用“双重加锁”,在getInstance()中减少使用同步

双重加锁不适用于 1.4 及更早版本的Java!如果不能使用Java 5, 而必须使用旧版的Java,就请不要使用此技巧实现单件模式。

public class Singleton{
  // volatile 关键字确保,当uniqueInstance 变量被初始化成 Singleton 实例时,多个线程正确地处理uniqueInstance 变量
  private volatile static Singleton uniqueInstance;

  private Singleton(){}

  public static Singleton getInstance(){
     if(uniqueInstance == null){
        synchronized (Singleton.class){
          if (uniqueInstance == null){
             uniqueInstance  = new Singleton();
          }
        }
     }
  }
}

六、命令模式

命令模式:将“请求”封装成对象,以便使用不同的请求、队列或者日志来参数化其他对象。命令模式也支持可撤销的操作。

七、适配器模式

适配器模式:将一个类的接口,转换成客户期望的另一个接口。适配器让原本接口不兼容的类可以合作无间。

客户和被适配者是解耦的,一个不知道另一个。

应该区分“装配者模式” 和“适配器模式”。

当需要使用一个现有的类而其接口并不符合你的需求时,就使用适配器。

适配器模式有两种形式:对象适配器和类适配器。类适配器需要用到多重继承(不合适在Java中使用)。

适配器将一个对象包装起来以改变其接口;装饰者将一个对象包装起来以增加新的行为和责任;而外观将一群对象“包装”起来以简化其接口。

八、外观模式

外观模式:提供了一个统一的接口,用来访问子系统中的一群接口。外观规定了一个高层接口,让子系统更容易使用。

外观模式不只是简化了接口,也将客户从组件的子系统中解耦。

外观和适配器可以包装许多类,但是外观的意图是简化接口,二适配器的意图是将接口转换成不同接口。

当需要简化并统一一个很大的接口或一群复杂的接口时,使用外观。

九、模板方法模式

模板方法模式:在以一个方法中定义算法的骨架,而将一些步骤延迟到子类中,模板方法使得子类可以在不改变算法结构的情况下,重新定义算法中的某些步骤。

模板方法定义了一个算法的定义,并允许子类为一个或多个步骤提供实现。

模板方法模式的抽象类可以定义具体方法、抽象方法和钩子。钩子是一种方法,它在抽象类中不做事,或者只做默认的事情,子类可以将模板方法声明为final。

十、迭代器模式

迭代器模式:提供一种方法顺序访问一个聚合队形中的各个元素,而又不暴露其内部的表示。

“集合”(collection),指的是一群对象,其存储方式可以是各式各样的数据结构,例如:列表、数组、散列表,无论什么方式存储一律视为集合,有时候也被称为聚合(aggregate)。

迭代器分为:内部迭代器和外部迭代器

放在gitHub上的案例:   printMenu是一个内部迭代器的例子:com.hef.design08.componentmodel.Waitress#printMenu;

printVegetarianMenu是一个外部迭代器的例子:com.hef.design08.componentmodel.Waitress#printVegetarianMenu

迭代器设计模式

十一、组合模式

组合模式: 允许你将对象组合成树形结构来表现“整体/部分”层次结构。组合能让客户以一致的方式处理个别对象以及对象组合。

组合模式让我们能用树形方式创建对象的结构,树里面包含了组合以及个别的对象。

迭代

组合模式

十二、状态模式

状态模式:允许对象在内部状态改变时改变它的行为,对象看起来好像修改了它的类。

状态模式

十三、代理模式

代理模式:为另一个对象提供一个替身或占位符以控制对这个对象的访问。

使用代理模式创建代表(representative)对象,让代表对象控制某对象的访问,被代理的对象可以是远程的对象、创建开销大的对象或需要安全控制的对象。

13.1 远程代理

(这里使用java RMI方案,未能实现)

远程代理作为另一个JVM上对象的本地代理。调用代理的方法,会被代理利用网络转发到远程执行,并且结果会通过网络返回给代理,再由代理将结果转给客户。

代理模式

13.2 虚拟代理

虚拟代理作为创建开销大的对象的代表。虚拟代理经常知道我们真正需要一个对象的时候才创建它。当对象在创建前和创建中时,由虚拟代理来扮演对象的替身。对象创建之后,代理会将请求直接委托给对象。

实例:

1)ImageProxy首先创建一个ImageIcon,然后开始从网络URL上加载图像;

2)在加载的过程中,ImageProxy显示“CD封面加载中,请稍等。。。”;

3)当图像加载完毕,ImageProxy把所有方法调用委托给真正的ImageIcon,这些方法包括了paintIcon(),getWidth(),和getHeight();

4)如果用户请求新的图像,我们就创建新的代理,重复这样的过程。

详细代码在github上

   @Override
    public void paintIcon(final Component c, Graphics g, int x, int y) {
        if(imageIcon!=null){
            // 如果已经有icon, 就告诉它画出自己
            imageIcon.paintIcon(c, g, x, y);
        } else {
            // 否则显示,加载中
            g.drawString("CD 加载中,请稍后。。。", x + 300, y+190);
            if(!retrieving){
                retrieving = true;
                retrievalThread = new Thread(new Runnable() {
                    @Override
                    public void run() {
                        imageIcon = new ImageIcon(imageURL, "CD Cover");
                        logger.info(imageIcon.getIconHeight() + ", " + imageIcon.getIconWidth());
                        c.repaint();
                    }
                });
                retrievalThread.start();
            }
        }
    }

13.3 动态代理

代理类是在运行时创建的,我们称这个Java技术为:动态代理。

动态代理类图

第一步: 创建InvocationHandler

InvocationHandler 实现了代理的行为。

public class NonOwnerInvocationHandler implements InvocationHandler {

    private PersonBean person;

    public NonOwnerInvocationHandler(PersonBean person) {
        this.person = person;
    }

    @Override
    public Object invoke(Object proxy, Method method, Object[] args) throws IllegalAccessException {
        try {

            if (method.getName().equals("setHotOrNotRating")) {
                return method.invoke(person, args);
            }else if(method.getName().startsWith("set")){
                throw new IllegalAccessException();
            }else if(method.getName().startsWith("get")){
                return method.invoke(person, args);
            }
        }catch (InvocationTargetException e){
            e.printStackTrace();
        }
        return null;
    }
}

第二步:写代码创建动态代理

    /**
     * 创建 OwnerInvocationHandler 的代理
     *
     * @param person
     * @return
     */
    public PersonBean getOwnerProxy(PersonBean person) {
        return (PersonBean) Proxy.newProxyInstance(
                person.getClass().getClassLoader(),
                person.getClass().getInterfaces(),
                new OwnerInvocationHandler(person));
    }

第三步:利用适合的代理包装任何对象

 

十四、复合模式

复合模式在一个解决方案中结合两个或多个模式,已解决一般或重复发生的问题。

 

 

 

 

 

 

 

 

 

 

 

 

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值