没有杯子的世界:OOP设计思想的应用实践

这篇具有很好参考价值的文章主要介绍了没有杯子的世界:OOP设计思想的应用实践。希望对大家有所帮助。如果存在错误或未考虑完全的地方,请大家不吝赐教,您也可以点击"举报违法"按钮提交疑问。

最近看到一个有趣的问题:Person类具有Hand,Hand可以操作杯子Cup,但是在石器时代是没有杯子的,这个问题用编程怎么解决?

简单代码实现

我们先用简单代码实现原问题:


@Data
public class Person {
    private final String name;
    private Hand hand = new Hand();

    private Mouth mouth = new Mouth();

    private static class Hand {
        // 为了简化问题,用字符串表示复杂的方法实现,这些方法极有可能具有副作用
        String holdCup() {
            return "hold a cup...";
        }

        String refillCup() {
            return "refill the coffee cup...";
        }
    }

    private static class Mouth {
        String drinkCoffee() {
            return "take a cup of coffee";
        }
    }

    public String drinkCoffee() {
        return String.join("\n",
                hand.refillCup(),
                hand.holdCup(),
                mouth.drink()
        );
    }
    // 略去其他方法,run(), work(), eat()...

    public static void main(String[] args) {
        Person eric = new Person("Eric");
        System.out.println("eric.drinkCoffee() = " + eric.drinkCoffee());
    }
}

良好的代码设计经常面向接口编程,我们抽取出接口如下:

public interface Person {
    String drinkCoffee();
    // 略去其他方法,run(), work(), eat()...

    interface Hand {
        String holdCup();

        String refillCup();
    }

    interface Mouth {
        String drinkCoffee();
    }
}

@Data
public class DefaultPerson implements Person {
    private final String name;
    private Hand hand = new DefaultHand();

    private Mouth mouth = new DefaultMouth();

    private static class DefaultHand implements Hand {
        @Override
        public String holdCup() {
            return "hold a cup...";
        }

        @Override
        public String refillCup() {
            return "refill the coffee cup...";
        }
    }

    private static class DefaultMouth implements Mouth {
        @Override
        public String drinkCoffee() {
            return "take a cup of coffee";
        }
    }

    @Override
    public String drinkCoffee() {
        return String.join("\n",
                hand.refillCup(),
                hand.holdCup(),
                mouth.drinkCoffee()
        );
    }

    public static void main(String[] args) {
        Person eric = new DefaultPerson("eric");
        System.out.println("eric.drinkCoffee() = " + eric.drinkCoffee());
    }
}

完事具备,现在我们来思考下这个问题: 问题的关键在于drinkCoffee方法,现在这个方法调用的结果是不对的,因为方法的调用依据了 DefaultPerson 之外的变量,即是否处于石器时代。 我们先看一个不好的实现:


@Value
public class BadPersonImpl implements Person {
    String name;
    boolean isInStoneEra;

    @Override
    public String drinkCoffee() {
        if (isInStoneEra) {
            return String.format("%s cannot drink, because there is no cup in the era.", getName());
        }
        return "refill the coffee cup..." + "hold a cup..." + "take a cup of coffee.";
    }

    public static void main(String[] args) {
        Person eric = new BadPersonImpl("Eric", true);
        System.out.println("eric.drinkCoffee() = " + eric.drinkCoffee());
    }
}

这段代码的问题是所有的内容都写死了,所有的代码都在一块,无法复用和拓展。

当然,如果说本来 Person 的实现就简单,新需求并不多,用这种方法也不是不可以。

问题分析&解决方法

不过,大部分情况下如果我们最开始这么写,把自己的路堵死了,当有新需求时,之后的修改极有可能发展成 if-else 套娃地狱,一个方法越写越多,越写越乱, 逻辑复杂到自己把自己都绕死了,最后实在受不了了,重写整个方法或类。

为什么我的代码中新加了 Mouth 这个类?

因为如果Person中有Hand这个类,通常说明 Hand类 有自己独立的实现,行为比较复杂,Person 实现的行为比较复杂, 加入了 Mouth 是为了说明 Person 类的复杂性,Person 是一个抽象工厂。

正确的做法应该考虑设计中的变量和不变量:

  1. 人所处的时代是变化的,时代影响人的行为
  2. 人的行为可以独立变化,即人具有hand、mouth等,其使用各个组件进行某些行为。
  3. 人的组件hand、mouth可以独立变化

不变:

  1. 时代一旦确定就不会更改(无需使用状态模式)
  2. Person的组件一旦确定就不会更改
  3. Person 和 Era 独立扩展

由此我们得出结论,Person 和 Era 要实现解耦。

interface EraEnvironment {
    default boolean hasCup() {
        return true;
    }
}

class ModernEra implements EraEnvironment {
}

class StoneAge implements EraEnvironment {
    @Override
    public boolean hasCup() {
        return false;
    }
}

// 基于组合的实现
@Value
class PersonInEra implements Person {
    Person person;
    EraEnvironment era;

    @Override
    public String drinkCoffee() {
        if (era.hasCup()) {
            return person.drinkCoffee();
        }
        return String.format("%s cannot drink, because there is no cup in the era.", person.getName());
    }

    @Override
    public String getName() {
        return person.getName();
    }

    public static void main(String[] args) {
        PersonInEra eric = new PersonInEra(new DefaultPerson("Eric"), new StoneAge());
        System.out.println("eric.drinkCoffee() = " + eric.drinkCoffee());
    }
}

进一步优化成协调者模式,可以保证各个 Colleague 类(Person、EraEnvironment)独立扩展。

如果以后还有影响 Person 行为的变量,比如天气、心情等,可以引入新的协调者。

可以看出,随着需求的增多,协调者可能越来越多,此时我们就需要重新进行分析,哪些条件可以看做Person的固有属性,对Person进行重构。

// 优化抽取出抽象类
class PersonInEra extends AbstractPersonInEra {
    public PersonInEra(Person person, EraEnvironment era) {
        super(person, era);
    }

    @Override
    public String drinkCoffee() {
        if (getEra().hasCup()) {
            return getPerson().drinkCoffee();
        }
        return String.format("%s cannot drink, because there is no cup in the era.", getName());
    }

    public static void main(String[] args) {
        PersonInEra eric = new PersonInEra(new DefaultPerson("Eric"), new StoneAge());
        System.out.println("eric.drinkCoffee() = " + eric.drinkCoffee());
    }
}

public abstract class AbstractPersonInEra implements Person {
    private final Person person;
    private final EraEnvironment era;

    public AbstractPersonInEra(Person person, EraEnvironment era) {
        this.person = person;
        this.era = era;
    }

    @Override
    public String getName() {
        return person.getName();
    }

    protected Person getPerson() {
        return person;
    }

    protected EraEnvironment getEra() {
        return era;
    }

    @Override
    public abstract String drinkCoffee();
}

面向对象原则分析

当然,根据对需求的不同理解和对未来需求的预期,我们可能选择不同的实现,这个问题还有可能用状态模式、策略模式等实现,不同的方法有优点也有缺点; 如果在面试中遇到这样的问题,一定要跟面试官明确背景和需求。

我们使用面向对象的基本原则分析下改动前后的代码:

1.单一职责原则(SRP):一个类/方法应该只有一个职责。

满足。以 PersonInEra::drinkCoffee 为例,其只负责根据环境,对调用方法进行选择。

2.开放封闭原则(OCP):软件实体应该对扩展开放,对修改关闭。

满足。对扩展开发不必多说,使用接口或抽象类都方便了拓展。

3.里氏替换原则(LSP):子类对象应该能够替换其父类对象并保持系统的行为正确性。

满足。我们使用时声明类型为接口 Person,使用的实例为其具体实现。

4.依赖倒置原则(DIP):高层模块不应该依赖于底层模块,而是应该通过抽象进行交互。

满足。client 使用了Person, Person的不同实现间的依赖都是接口或抽象类。 一个实体类抽象出接口是一个万金油式的好方法。

5.接口隔离原则(ISP):一个类对另一个类的依赖应该建立在最小的接口上。

满足。比如 AbstractPersonInEra 依赖的是 Person接口,这个接口并不包含其他不必要的方法。

6.合成/聚合复用原则(CARP):优先使用对象合成或聚合,而不是继承来实现代码复用。

满足。AbstractPersonInEra 使用的是组合实现。

7.迪米特法则(LoD):一个对象应该对其它对象保持最小的了解。 满足。这里还是看出了使用接口的好处,AbstractPersonInEra 只知道自己依赖了 Person 和 EraEnvironment, 对于依赖对象的实现一无所知。

策略模式

最后,你可以自己写个策略模式,和我写的策略模式比较一下,从面向对象设计的角度分析其优劣。

使用策略模式编写的代码如下:文章来源地址https://www.toymoban.com/news/detail-419712.html

// 策略模式,不改变原 DefaultPerson 的实现
@FunctionalInterface
public interface DrinkStrategy {
    String drink();
}

public final class Persons {
    private Persons(){}
    @NotNull
    private static DrinkStrategy stoneEraSupport(Person person, EraEnvironment era) {
        return () -> {
            if (era.hasCup()) {
                return person.drinkCoffee();
            }
            return String.format("%s cannot drink, because there is no cup in the era.", person.getName());
        };
    }

    // 工厂方法创建复杂对象
    @NotNull
    public static Person stoneAgeSupportWithNameAndEra(String name, EraEnvironment era) {
        DefaultPerson oriPerson = new DefaultPerson(name);
        return new StrategicPerson(oriPerson, stoneEraSupport(oriPerson, era));
    }
}

@Value
public class StrategicPerson implements Person {
    // 使用组合
    Person person;

    // 支持多种策略,拓展性好
    DrinkStrategy drinkStrategy;

    @Override
    public String drinkCoffee() {
        return drinkStrategy.drink();
    }

    // 除需要更改的方法外,其他实现委托给原 Person. 比较烦的是:需要委托的方法多的话,都要单独编写方法
    @Override
    public String getName() {
        return person.getName();
    }

    public static void main(String[] args) {
        Person eric = Persons.stoneAgeSupportWithNameAndEra("eric", new StoneAge());
        System.out.println("eric.drinkCoffee() = " + eric.drinkCoffee());
    }
}

到了这里,关于没有杯子的世界:OOP设计思想的应用实践的文章就介绍完了。如果您还想了解更多内容,请在右上角搜索TOY模板网以前的文章或继续浏览下面的相关文章,希望大家以后多多支持TOY模板网!

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处: 如若内容造成侵权/违法违规/事实不符,请点击违法举报进行投诉反馈,一经查实,立即删除!

领支付宝红包 赞助服务器费用

相关文章

  • 【Spring】核心与设计思想

     哈喽,哈喽,大家好~ 我是你们的老朋友: 保护小周ღ   谈起Java 圈子里的框架,最年长最耀眼的莫过于 Spring 框架啦,如今已成为最流行、最广泛使用的Java开发框架之一。不知道大家有没有在使用 Spring 框架的时候思考过这些问题, 什么是框架?Spring 是什么?如何理解

    2024年02月08日
    浏览(44)
  • 闪电网络协议设计思想剖析

    闪电网络可能是比特币之上部署的最受期待的技术创新。闪电网络,为由 Joseph Poon 和 Tadge Dryja 于2015年首次提出的支付层,承诺支持: 用户之间几乎无限数量的链下交易, 几乎免费, 同时利用比特币提供的安全性。 2016年时,至少三个公司——Poon 和 Dryja 的 Lightning、 Block

    2024年03月20日
    浏览(61)
  • Spring 核心与设计思想

    ✏️作者:银河罐头 📋系列专栏:JavaEE 🌲 “种一棵树最好的时间是十年前,其次是现在” 通常所说的 Spring 指的是 Spring Framework(Spring 框架)。 Spring 是包含多种工具方法的 IoC 容器。 IoC(Inversion of Control): 控制反转 \\\"控制反转\\\"又是什么意思? 下面以一个程序来举例。 假如我

    2024年02月02日
    浏览(55)
  • Spring框架核心与设计思想

    我们一般所说的Spring指的是Spring Framework(Spring 框架),它是一个开源的框架,Spring支持广泛的应用场景,它可以让Java企业级的应用程序开发变得更简单,官方一点的回答:spring是J2EE应用程序框架,是轻量级的IoC和AOP的容器框架,主要是针对javaBean的生命周期进行管理的轻量级

    2023年04月15日
    浏览(44)
  • Spring框架概述及核心设计思想

    我们通常所说的 Spring 指的是 Spring Framework(Spring 框架),它是⼀个开源框架,有着活跃而庞大的社区,这就是它之所以能长久不衰的原因;Spring 支持广泛的应用场景,它可以让 Java 企业级的应用程序开发起来更简单。 用⼀句话概括 Spring: Spring 框架是包含了众多工具方法的

    2024年02月16日
    浏览(38)
  • 从架构设计思想出发看Flutter

    Flutter 是一种流行的移动应用程序开发框架,它的设计特点之一是可以使用单一代码库构建 iOS 和 Android 应用程序。然而,对于功能比较多、模块比较复杂的应用程序,仅凭单一的代码库就可能导致代码的复杂性和维护难度的增加。在这种情况下,通过合适的应用程序架构设计

    2024年02月07日
    浏览(72)
  • 【JavaEE进阶】Spring核心与设计思想

    我们通常所说的 Spring 指的是 Spring Framework (Spring 框架),它是一个轻量级的 Java 开源框架,有着活跃庞⼤的社区。Spring 是为了解决企业应用开发的复杂性而创建的,不仅⽀持⼴泛的应⽤场景,还让 Java 企业级的应⽤程序开发更加简单。 如何简单地使⽤⼀句话概括 Spring:

    2024年02月13日
    浏览(48)
  • Cola4.0 - DDD 设计思想

    COLA提供了一整套代码架构,拿来即用。 其中包含了很多架构设计思想,包括讨论度很高的领域驱动设计DDD等。 COLA 的分层是一种经过改良的三层架构,主要是讲传统的业务逻辑层拆分为展示层、应用层、领域层和基础设施层。 展示层(Presentation Layer):负责以 Rest 的风格接

    2024年02月08日
    浏览(38)
  • 【JavaEE进阶】 Spring核⼼与设计思想

    我们通常所说的 Spring 指的是 Spring Framework(Spring 框架),它是⼀个开源框架,有着活跃⽽庞⼤的社区,这就是它之所以能⻓久不衰的原因。Spring ⽀持⼴泛的应⽤场景,它可以让 Java 企业级的应⽤程序开发起来更简单。 ⽤⼀句话概括 Spring: Spring 是包含了众多⼯具⽅法的 I

    2024年02月04日
    浏览(43)
  • 数据仓库之建模理论以及仓库设计思想

    数据仓库是一个为数据分析而设计的企业级数据管理系统。数据仓库可集中、整合多个信息源的大量数据,借助数据仓库的分析能力,企业可从数据中获得宝贵的信息进而改进决策。同时,随着时间的推移,数据仓库中积累的大量历史数据对于数据科学家和业务分析师也是十

    2023年04月15日
    浏览(61)

觉得文章有用就打赏一下文章作者

支付宝扫一扫打赏

博客赞助

微信扫一扫打赏

请作者喝杯咖啡吧~博客赞助

支付宝扫一扫领取红包,优惠每天领

二维码1

领取红包

二维码2

领红包