设计模式之美
王争
前Google工程师,《数据结构与算法之美》专栏作者
立即订阅
17460 人已学习
课程目录
已更新 20 讲 / 共 100 讲
0/6登录后,你可以任选6讲全文学习。
开篇词 (1讲)
开篇词 | 一对一的设计与编码集训,让你告别没有成长的烂代码!
免费
设计模式学习导读 (3讲)
01 | 为什么说每个程序员都要尽早地学习并掌握设计模式相关知识?
02 | 从哪些维度评判代码质量的好坏?如何具备写出高质量代码的能力?
03 | 面向对象、设计原则、设计模式、编程规范、重构,这五者有何关系?
设计原则与思想:面向对象 (11讲)
04 | 理论一:当谈论面向对象的时候,我们到底在谈论什么?
05 | 理论二:封装、抽象、继承、多态分别可以解决哪些编程问题?
06 | 理论三:面向对象相比面向过程有哪些优势?面向过程真的过时了吗?
07 | 理论四:哪些代码设计看似是面向对象,实际是面向过程的?
08 | 理论五:接口vs抽象类的区别?如何用普通的类模拟抽象类和接口?
09 | 理论六:为什么基于接口而非实现编程?有必要为每个类都定义接口吗?
10 | 理论七:为何说要多用组合少用继承?如何决定该用组合还是继承?
11 | 实战一(上):业务开发常用的基于贫血模型的MVC架构违背OOP吗?
12 | 实战一(下):如何利用基于充血模型的DDD开发一个虚拟钱包系统?
13 | 实战二(上):如何对接口鉴权这样一个功能开发做面向对象分析?
14 | 实战二(下):如何利用面向对象设计和编程开发接口鉴权功能?
设计原则与思想:设计原则 (3讲)
15 | 理论一:对于单一职责原则,如何判定某个类的职责是否够“单一”?
16 | 理论二:如何做到“对扩展开放、修改关闭”?扩展和修改各指什么?
17 | 理论三:里式替换(LSP)跟多态有何区别?哪些代码违背了LSP?
不定期加餐 (2讲)
加餐一 | 用一篇文章带你了解专栏中用到的所有Java语法
加餐二 | 设计模式、重构、编程规范等相关书籍推荐
设计模式之美
登录|注册

07 | 理论四:哪些代码设计看似是面向对象,实际是面向过程的?

王争 2019-11-18
上一节课,我们提到,常见的编程范式或者说编程风格有三种,面向过程编程、面向对象编程、函数式编程,而面向对象编程又是这其中最主流的编程范式。现如今,大部分编程语言都是面向对象编程语言,大部分软件都是基于面向对象编程这种编程范式来开发的。
不过,在实际的开发工作中,很多同学对面向对象编程都有误解,总以为把所有代码都塞到类里,自然就是在进行面向对象编程了。实际上,这样的认识是不正确的。有时候,从表面上看似是面向对象编程风格的代码,从本质上看却是面向过程编程风格的。
所以,今天,我结合具体的代码实例来讲一讲,有哪些看似是面向对象,实际上是面向过程编程风格的代码,并且分析一下,为什么我们很容易写出这样的代码。最后,我们再一起辩证思考一下,面向过程编程是否就真的无用武之地了呢?是否有必要杜绝在面向对象编程中写面向过程风格的代码呢?
好了,现在,让我们正式开始今天的学习吧!

哪些代码设计看似是面向对象,实际是面向过程的?

在用面向对象编程语言进行软件开发的时候,我们有时候会写出面向过程风格的代码。有些是有意为之,并无不妥;而有些是无意为之,会影响到代码的质量。下面我就通过三个典型的代码案例,给你展示一下,什么样的代码看似是面向对象风格,实际上是面向过程风格的。我也希望你通过对这三个典型例子的学习,能够做到举一反三,在平时的开发中,多留心一下自己编写的代码是否满足面向对象风格。
取消
完成
0/1000字
划线
笔记
复制
© 版权归极客邦科技所有,未经许可不得传播售卖。 页面已增加防盗追踪,如有侵权极客邦将依法追究其法律责任。
该试读文章来自付费专栏《设计模式之美》,如需阅读全部文章,
请订阅文章所属专栏。
立即订阅
登录 后留言

精选留言(142)

  • 辣么大
    不想往下看的请看第一句就好:贫血模型流行的原因:实现简单和上手快。

    具体解释慢慢看:

    ”贫血模型“的开发模式为什么会流行?

    1、实现简单。Object仅仅作为传递数据的媒介,不用考虑过多的设计方面,将核心业务逻辑放到service层,用Hibernate之类的框架一套,完美解决任务。
    2、上手快。使用贫血模式开发的web项目,新来的程序员看看代码就能“照猫画虎”干活了,不需要多高的技术水平。所以很多程序员干了几年,仅仅就会写CURD。
    3、一些技术鼓励使用贫血模型。例如J2EE Entity Beans,Hibernate等。

    总结:各种模型的好坏讨论一直不断,企业需要的是使用合适的技术把任务完成,从这个角度来说当下管用模型就是好模型。当然我们也要持开放的心态接受新的技术和思想,并结合业务的实际需要选择合适的技术。

    概念解释:

    贫血模型(Anemic Domain Model由
    MatinFowler提出)又称为失血模型,是指domain object仅有属性的getter/setter方法的纯数据类,将所有类的行为放到service层。原文他是这么说的“By pulling all the behavior out into services, however, you essentially end up with Transaction Scripts, and thus lose the advantages that the domain model can bring. ”他的原文我放上来了,英文好的同学可以看看:https://martinfowler.com/bliki/AnemicDomainModel.html 。 我觉得他有点学者气太重,这篇博客他都不知道为啥贫血模型会流行(I don't know why this anti-pattern is so common)。
    2019-11-18
    7
    90
  • Jxin
    1.用shell实现自动化脚本做的服务编排,一般都是面向过程,一步一步的。而k8s的编排却是面向对象的,因为它为这个顺序流抽象出了很多角色,将原本一步一步的顺序操作转变成了多个角色间的轮转和交互。

    2.从接触ddd才走出javaer举面向对象旗,干面向过程勾当的局面。所谓为什么“充血模型”不流行,我认为不外呼两个。一,规范的领域模型对于底层基础架构来说并不友好(缺少setget),所以会导致规范的领域模型与现有基础架构不贴合,切很难开发出完全贴合的基础架构,进而引深出,合理的业务封装却阻碍关于复用通用抽象的矛盾。二,合理的业务封装,需要在战略上对业务先做合理的归类分割和抽象。而这个前置条件很少也不好达成。进而缺少前置设计封装出来的“充血模型”会有种四不像的味道,反而加剧了业务的复杂性,还不如“贫血模型”来得实用。事实上快节奏下,前置战略设计往往都是不足的,所以想构建优秀的“充血模型”架构,除了要对业务领域和领域设计有足够的认知,在重构手法和重构意愿上还要有一定讲究和追求,这样才能让项目以“充血模型”持续且良性的迭代。

    3.“充血模型”相对于“贫血模型”有什么好处?从我的经验来看,可读性其实可能“贫血模型”还好一点,这也可能有思维惯性的原因在里面。但从灵活和扩展性来说“充血模型”会优秀很多,因为好的“充血模型”往往意味着边界清晰(耦合低),功能内敛(高内聚)。这一块老师怎么看?

    作者回复: 说的非常好👍

    2019-11-18
    3
    47
  • 猫切切切切切
    总的来说,使用面向对象风格编写的面向过程代码有如下特点:

    1. 使用了类,但是
    2. 要么完全没有封装(比如数据和操作分离的贫血模式)
    3. 要么破坏了封装(比如滥用 getter 或 setter)
    4. 要么完全没有抽象(大而全的 Constants 或 Utils 类)
    5. 要么封装或抽象不完全(比如类实例化后,或者子类继承后,需要自己管理其内部某些属性或状态)

    平时应该多留心代码是否存在上述特征。

    文中没有举封装或抽象不完全的例子,这里举一个。

    比如一个实现某种业务需求(如与某种类型设备通讯的应用协议)的 tcp 或 udp 服务器;

    实例化后还需要自己管理其协议相关的就绪状态(ready属性);使你不得不对其再封装一层,并抽象其连接、断开等方法使其自动进行就绪状态的管理;

    每个继承都这么封装一遍,就会有大量重复的代码,而且其实类的实例化者或继承者并不需要也不应该关心就绪状态的管理,所以没有达到就绪状态管理的封装。

    这就是一种不完全的封装。
    2019-11-18
    36
  • 黄林晴
    打卡
    看了今天的内容,发现自己三点都占了,😲
    遇到json数据使用Gsonformat转一下,默认生成所有get set方法,遇到统一使用的就会毫不犹豫定义工具类……,我有点怀疑自己是不是从未写过面相对象风格的代码
    2019-11-18
    6
    32
  • 嘉一
    个人觉得,MVC这种框架模式本质上与面向对象并不冲突。当我们在讨论面向对象的时候,我们究竟应该怎样去定义一个对象,究竟什么才能被我们看成是对象,是不是只有像某种物体,比如说一只鸟或者一只狗我们才能去把他定义为对象?我认为,MVC里面的三个部分Model 、Controller 、View 我们都能把他们单独的看成一个对象,比如说Model,本来它是数据单元,但是如果我们把他看做一个对象的话,里面存储的数据不就是我们对象里的属性么,而对于数据的二次加工处理等等操作不就是对象里的方法么?同理,对于View而言,里面小的view组件或者是其他的view不就是我们对象里面的属性,而对于不同的view组件或其他view的组合或者其他的处理操作不就是对象里面的方法么?所以说,不必死抠定义,数据就一定要和业务逻辑组成一个类云云。。。我们最后写出来的代码的目的就是,1.要解决问题;2.代码有可扩展性,可读性;3,代码解耦;

    作者回复: 说的没错,MVC跟贫血模型没直接关系。我后面在实战篇会讲到的。你的观点我基本都赞同。

    2019-11-18
    1
    20
  • 中年男子
    先说问题2:看似面向对象实际面向过程的例子真是数不胜数了, 工作语言C/C++,90%是C++,大体上老师在文中已经提到了,其他的我暂时也没想起来,但是滥用面向对象继承特性的代码我真是看到了太多
    问题1:C中可以用struct 来实现class,只是访问控制权限都是public。类中的成员函数可以通过指向操作结构体的函数指针来实现,实现封装,需要绑定数据、函数、函数指针。可以创建函数指针表,构造函数设置函数指针指向正确的操作函数,函数指针表作为对象访问函数的接口。操作结构体的这些函数(成员函数)不像C++中能直接访问数据成员,需要显示的传递操作对象给成员函数。
    继承:在派生类中维护一个基类对象的指针。这样派生类可以访问基类对象的数据。
    多态:在基类中维护一个派生类对象的指针。这样基类可以访问派生类对象的数据。
    C++中的多态,有一个对象销毁的问题。基类的析构函数必须是虚函数
    在C中,这可以通过使基类的删除函数指针指向派生类的删除函数,因为派生类的删除函数清楚派生类的数据和基类的数据
    2019-11-18
    1
    13
  • Daiver
    啊,写了这么久的MVC,竟然是面向过程编程。
    2019-11-18
    1
    13
  • 熊斌
    我们的项目三点都占,造成这个局面的原因我认为有以下几点:
    1、习惯用IDE的代码生成插件
    2、团队整体设计水平有限
    3、基于mvc模式开发的
    2019-11-18
    12
  • Jackey
    确实是个问题啊,现在流行的贫血模式每个项目都会用。用了贫血模式就会涉及BO和VO的转换,很多工具都需要全部字段的setter方法…感觉是个连环套啊。
    ps:用了这么久Java原来一直是面向过程编程,怪不得找不到对象
    2019-11-19
    10
  • Monday
    我去去去,自从知道lombok后,@Data注解每个实体类必用。。。
    2019-11-18
    2
    8
  • 青青子衿
    有时候写get,set也是无奈之举,比如集成spring的时候,框架要求属性提供get,set
    2019-11-18
    8
  • 中年男子
    接上条评论,代码实现如下,欢迎讨论
    typedef struct _Base Base;
    typedef void (*fptrDisplay)(Base*);
    typedef void (*fptrDelete)(Base*);
    void DisplayBase(Base* );
    void DeleteBase(Base*);
    typedef struct _Base
    {
    void* pDeriveObj;
    int a;
    int b;
    fptrDisplay Display;
    fptrDelete Delete;
    }Base;
    Base* new_base(int a, int b)
    {
    Base* pObj = NULL;
    pObj = (Base*)malloc(sizeof(Base));
    if (pObj == NULL)
    {
    return NULL;
    }
    // 当创建基类对象时指向自己
    pObj->pDeriveObj = pObj;
    pObj->a = a;
    pObj->b = b;
    pObj->Display = DisplayBase;
    pObj->Delete = DeleteBase;
    return pObj;
    }
    void DisplayBase(Base* base)
    {
    printf("member: a:%d\t b:%d\n", base->a, base->b);
    }
    void DeleteBase(Base* base)
    {
    printf("base destructor!\n");
    free(base);
    }
    typedef struct _Derive
    {
    Base* pBaseObj;
    int c;
    int d;
    }Derive;
    void DeriveDisplay(Base* base);
    void DeriveDelete(Base* base);
    Base* new_Derive(int a, int b, int c, int d)
    {
    Derive* pObj = NULL;
    Base* pBaseObj = new_base(a, b);
    pObj = malloc(sizeof(Derive));
    if (!pObj)
    {
    pBaseObj->Delete(pBaseObj);
    return NULL;
    }
    pBaseObj->pDeriveObj = pObj;
    pObj->pBaseObj = pBaseObj;
    pObj->c = c;
    pObj->d = d;
    pBaseObj->Display = DeriveDisplay;
    pBaseObj->Delete = DeriveDelete;
    return pBaseObj;
    }
    void DeriveDisplay(Base* base)
    {
    Derive* pDeriveObj = (Derive*)(base->pDeriveObj);
    printf("member:base:a:%d\t base:b:%d\nderive:c:%d\t derive:d:%d\n", base->a, base->b, pDeriveObj->c, pDeriveObj->d);
    }
    void DeriveDelete(Base* base)
    {
    printf("derive destructor!\n");
    free(base->pDeriveObj);
    free(base);
    }
    int main()
    {
    Base* pBase = new_base(1, 2);
    Base* pDerive = new_Derive(3, 4, 5, 6);
    pBase->Display(pBase);
    pDerive->Display(pDerive);
    pBase->Delete(pBase);
    pDerive->Delete(pDerive);
    }
    2019-11-18
    3
    5
  • DebugDog
    我全占了,今天才知道自己学的Java,天天在写面向过程😰
    2019-11-18
    5
  • Jeff.Smile
    有种上帝视角看自己的感觉!
    2019-11-18
    5
  • 梦倚栏杆
    1.现在因为使用封装好的框架,没有提供set方法,类的序列化会成为一个问题
    2.从理论上来说,数据和逻辑应该放在一起,但是数据的赋值往往可能依赖其他的service提供的数据,如果这样的话数据属性和纯粹依赖的service就会导致一个类的成员属性特别的多。
    3.对于一个具体现实对象而言,不同场景下可能关心的字段稍微有些不一样,对于此又该怎么处理呢?多个小对象,他们之间有无相关关系,有的话如何阐述,还是全部赋值完毕,都完整返回。当然可能具体场景具体分析,那是否有一个稍微通用的指导纲领
    期待老师关于面向对象的实战流程

    作者回复: 😄你说的后面都基本上有讲到

    2019-11-18
    5
  • hong
    习惯了从前端的参数直接使用 BeanUtils.copyProperties 映射到具体实体类,如果不直接提供set方法,有啥好的方法去组装数据呢

    作者回复: 给类本身提供一个copy方法呢

    2019-11-21
    4
  • 编程界的小学生
    1.get set 这个很好理解,但是我有很多疑问,比如有的属性理论上来讲不该添加set方法,那我怎么对他进行属性拷贝?比如两个vo进行拷贝属性值,还有作为接口参数,spring又怎么给他赋值?
    2.看完贫血模式那个知识点后,我懵了,我甚至不知道怎么才能写出面向对象的代码了,如果数据和业务不分离的话,那比如我多个业务接口需要同一份数据,难道要定义多份吗?我有点懵了😥😥😥

    作者回复: 1. 并没有说一定不能定义set方法,文章中说不要滥定义用不上的set方法
    2. 多个业务接口需要同一份数据?这个怎么理解呢?

    2019-11-18
    2
    4
  • 守拙
    今日的课堂讨论不会回答,尝试总结一下重点回顾的3个问题:

    1.getter, setter问题的本质类的可变性问题.<Effective Java>中明确提到,除非有必要,否则类应该设计为不可变(Immutable)的.

    2. Constants 和 Util类的问题本质是静态成员和静态方法问题.
    静态成员和静态方法违背面向对象设计(OOP)原则,但从整体项目角度讲,静态成员和静态方法的好处大于其坏处,所以它们确实有存在的意义.

    3.我对贫血模型的看法: 我是一名Android开发,日常使用的是MVC的变种MVP && MVVM模型.
    MVP和MVVM相比MVC要更靠近OOP思想,但面向过程思想的设计仍包含于其中。
    无论面向对象或面向过程,写出层次清晰,易扩展,易维护的代码才是目的。
    2019-11-18
    1
    3
  • James
    我想的是,老师说的基本上在中小公司都是这样子的,
    公司水平不高,上手快,对新手要求不高,可能导致很多人仅仅只会增删改查...
    2019-11-20
    2
  • 观弈道人
    1.Get到了Collections.unmodifiableList,这个很好。
    2.Constants分类很好,常量定义要根据复用级别,而放在不同的位置比较好。比如:有的放在当前类、父类、同一个模块中定义的Constants,公共模块中定义的Constants.
    3.dto/po中的getter/setter只是为了方便框架解析。带有业务方法的对象,需要慎重考虑getter/setter.
    2019-11-19
    2
收起评论
99+
返回
顶部