开发中滥用面向对象,你是否违背了编程原则
Switch 声明
Switch 声明(Switch Statements)
你有一个复杂的
switch
语句或if
序列语句。
问题原因
面向对象程序的一个最明显特征就是:少用 switch
和 case
语句。从本质上说,switch
语句的问题在于重复(if
序列也同样如此)。你常会发现 switch
语句散布于不同地点。如果要为它添加一个新的 case
子句,就必须找到所有 switch
语句并修改它们。面向对象中的多态概念可为此带来优雅的解决办法。
大多数时候,一看到 switch
语句,就应该考虑以多态来替换它。
解决方法
- 问题是多态该出现在哪?switch 语句常常根据类型码进行选择,你要的是“与该类型码相关的函数或类”,所以应该运用
提炼函数(Extract Method)
将switch
语句提炼到一个独立函数中,再以搬移函数(Move Method)
将它搬移到需要多态性的那个类里。 - 如果你的
switch
是基于类型码来识别分支,这时可以运用以子类取代类型码(Replace Type Code with Subclass)
或以状态/策略模式取代类型码(Replace Type Code with State/Strategy)
。 - 一旦完成这样的继承结构后,就可以运用
以多态取代条件表达式(Replace Conditional with Polymorphism)
了。 - 如果条件分支并不多并且它们使用不同参数调用相同的函数,多态就没必要了。在这种情况下,你可以运用
以明确函数取代参数(Replace Parameter with Explicit Methods)
。 - 如果你的选择条件之一是 null,可以运用
引入 Null 对象(Introduce Null Object)
。
收益
- 提升代码组织性。
何时忽略
- 如果一个
switch
操作只是执行简单的行为,就没有重构的必要了。 switch
常被工厂设计模式族(工厂方法模式(Factory Method)
和抽象工厂模式(Abstract Factory)
)所使用,这种情况下也没必要重构。
重构方法说明
提炼函数(Extract Method)
问题
你有一段代码可以组织在一起。
void printOwing() { printBanner(); //print details System.out.println("name: " + name); System.out.println("amount: " + getOutstanding()); }
解决
移动这段代码到一个新的函数中,使用函数的调用来替代老代码。
void printOwing() { printBanner(); printDetails(getOutstanding()); } void printDetails(double outstanding) { System.out.println("name: " + name); System.out.println("amount: " + outstanding); }
搬移函数(Move Method)
问题
你的程序中,有个函数与其所驻类之外的另一个类进行更多交流:调用后者,或被后者调用。
解决
在该函数最常引用的类中建立一个有着类似行为的新函数。将旧函数变成一个单纯的委托函数,或是旧函数完全移除。
以子类取代类型码(Replace Type Code with Subclass)
问题
你有一个不可变的类型码,它会影响类的行为。
解决
以子类取代这个类型码。
以状态/策略模式取代类型码(Replace Type Code with State/Strategy)
问题
你有一个类型码,它会影响类的行为,但你无法通过继承消除它。
解决
以状态对象取代类型码。
以多态取代条件表达式(Replace Conditional with Polymorphism)
问题
你手上有个条件表达式,它根据对象类型的不同而选择不同的行为。
class Bird { //... double getSpeed() { switch (type) { case EUROPEAN: return getBaseSpeed(); case AFRICAN: return getBaseSpeed() - getLoadFactor() * numberOfCoconuts; case NORWEGIAN_BLUE: return (isNailed) ? 0 : getBaseSpeed(voltage); } throw new RuntimeException("Should be unreachable"); } }
解决
将这个条件表达式的每个分支放进一个子类内的覆写函数中,然后将原始函数声明为抽象函数。
abstract class Bird { //... abstract double getSpeed(); } class European extends Bird { double getSpeed() { return getBaseSpeed(); } } class African extends Bird { double getSpeed() { return getBaseSpeed() - getLoadFactor() * numberOfCoconuts; } } class NorwegianBlue extends Bird { double getSpeed() { return (isNailed) ? 0 : getBaseSpeed(voltage); } } // Somewhere in client code speed = bird.getSpeed();
以明确函数取代参数(Replace Parameter with Explicit Methods)
问题
你有一个函数,其中完全取决于参数值而采取不同的行为。
void setValue(String name, int value) { if (name.equals("height")) { height = value; return; } if (name.equals("width")) { width = value; return; } Assert.shouldNeverReachHere(); }
解决
针对该参数的每一个可能值,建立一个独立函数。
void setHeight(int arg) { height = arg; } void setWidth(int arg) { width = arg; }
引入 Null 对象(Introduce Null Object)
问题
你需要再三检查某对象是否为 null。
if (customer == null) { plan = BillingPlan.basic(); } else { plan = customer.getPlan(); }
解决
将 null 值替换为 null 对象。
class NullCustomer extends Customer { Plan getPlan() { return new NullPlan(); } // Some other NULL functionality. } // Replace null values with Null-object. customer = (order.customer != null) ? order.customer : new NullCustomer(); // Use Null-object as if it's normal subclass. plan = customer.getPlan();
临时字段
临时字段(Temporary Field)的值只在特定环境下有意义,离开这个环境,它们就什么也不是了。
问题原因
有时你会看到这样的对象:其内某个实例变量仅为某种特定情况而设。这样的代码让人不易理解,因为你通常认为对象在所有时候都需要它的所有变量。在变量未被使用的情况下猜测当初设置目的,会让你发疯。 通常,临时字段是在某一算法需要大量输入时而创建。因此,为了避免函数有过多参数,程序员决定在类中创建这些数据的临时字段。这些临时字段仅仅在算法中使用,其他时候却毫无用处。 这种代码不好理解。你期望查看对象字段的数据,但是出于某种原因,它们总是为空。
解决方法
- 可以通过
提炼类(Extract Class)
将临时字段和操作它们的所有代码提炼到一个单独的类中。此外,你可以运用以函数对象取代函数(Replace Method with Method Object)
来实现同样的目的。 引入 Null 对象(Introduce Null Object)
在“变量不合法”的情况下创建一个 null 对象,从而避免写出条件表达式。
收益
- 更好的代码清晰度和组织性。
重构方法说明
提炼类(Extract Class)
问题
某个类做了不止一件事。
解决
建立一个新类,将相关的字段和函数从旧类搬移到新类。
以函数对象取代函数(Replace Method with Method Object)
问题
你有一个过长函数,它的局部变量交织在一起,以致于你无法应用提炼函数(Extract Method) 。
class Order { //... public double price() { double primaryBasePrice; double secondaryBasePrice; double tertiaryBasePrice; // long computation. //... } }
解决
将函数移到一个独立的类中,使得局部变量成了这个类的字段。然后,你可以将函数分割成这个类中的多个函数。
class Order { //... public double price() { return new PriceCalculator(this).compute(); } } class PriceCalculator { private double primaryBasePrice; private double secondaryBasePrice; private double tertiaryBasePrice; public PriceCalculator(Order order) { // copy relevant information from order object. //... } public double compute() { // long computation. //... } }
引入 Null 对象(Introduce Null Object)
问题
你需要再三检查某对象是否为 null。
if (customer == null) { plan = BillingPlan.basic(); } else { plan = customer.getPlan(); }
解决
将 null 值替换为 null 对象。
class NullCustomer extends Customer { Plan getPlan() { return new NullPlan(); } // Some other NULL functionality. } // Replace null values with Null-object. customer = (order.customer != null) ? order.customer : new NullCustomer(); // Use Null-object as if it's normal subclass. plan = customer.getPlan();
异曲同工的类
异曲同工的类(Alternative Classes with Different Interfaces)
两个类中有着不同的函数,却在做着同一件事。
问题原因
这种情况往往是因为:创建这个类的程序员并不知道已经有实现这个功能的类存在了。
解决方法
- 如果两个函数做同一件事,却有着不同的签名,请运用
函数改名(Rename Method)
根据它们的用途重新命名。 - 运用
搬移函数(Move Method)
、添加参数(Add Parameter)
和令函数携带参数(Parameterize Method)
来使得方法的名称和实现一致。 - 如果两个类仅有部分功能是重复的,尝试运用
提炼超类(Extract Superclass)
。这种情况下,已存在的类就成了超类。 - 当最终选择并运用某种方法来重构后,也许你就能删除其中一个类了。
收益
- 消除了不必要的重复代码,为代码瘦身了。
- 代码更易读(不再需要猜测为什么要有两个功能相同的类)。
何时忽略
- 有时合并类是不可能的,或者是如此困难以至于没有意义。例如:两个功能相似的类存在于不同的 lib 库中。
重构方法说明
函数改名(Rename Method)
问题
函数的名称未能恰当的揭示函数的用途。
class Person { public String getsnm(); }
解决
修改函数名。
class Person { public String getSecondName(); }
搬移函数(Move Method)
问题
你的程序中,有个函数与其所驻类之外的另一个类进行更多交流:调用后者,或被后者调用。
解决
在该函数最常引用的类中建立一个有着类似行为的新函数。将旧函数变成一个单纯的委托函数,或是旧函数完全移除。
添加参数(Add Parameter)
问题 某个函数需要从调用端得到更多信息。
class Customer { public Contact getContact(); }
解决 为此函数添加一个对象函数,让改对象带进函数所需信息。
class Customer { public Contact getContact(Date date); }
令函数携带参数(Parameterize Method)
问题
若干函数做了类似的工作,但在函数本体中却包含了不同的值。
解决
建立单一函数,以参数表达哪些不同的值。
提炼超类(Extract Superclass)
问题
两个类有相似特性。
解决
为这两个类建立一个超类,将相同特性移至超类。
被拒绝的馈赠
被拒绝的馈赠(Refused Bequest)
子类仅仅使用父类中的部分方法和属性。其他来自父类的馈赠成为了累赘。
问题原因
有些人仅仅是想重用超类中的部分代码而创建了子类。但实际上超类和子类完全不同。
解决方法
- 如果继承没有意义并且子类和父类之间确实没有共同点,可以运用
以委托取代继承(Replace Inheritance with Delegation)
消除继承。 - 如果继承是适当的,则去除子类中不需要的字段和方法。运用
提炼超类(Extract Superclass)
将所有超类中对于子类有用的字段和函数提取出来,置入一个新的超类中,然后让两个类都继承自它。
收益
- 提高代码的清晰度和组织性。
重构方法说明
以委托取代继承(Replace Inheritance with Delegation)
问题
某个子类只使用超类接口中的一部分,或是根本不需要继承而来的数据。
解决
- 在子类中新建一个字段用以保存超类;
- 调整子类函数,令它改而委托超类;
- 然后去掉两者之间的继承关系。
提炼超类(Extract Superclass)
问题
两个类有相似特性。
解决
为这两个类建立一个超类,将相同特性移至超类。
低调大师中文资讯倾力打造互联网数据资讯、行业资源、电子商务、移动互联网、网络营销平台。
持续更新报道IT业界、互联网、市场资讯、驱动更新,是最及时权威的产业资讯及硬件资讯报道平台。
转载内容版权归作者及来源网站所有,本站原创内容转载请注明来源。
- 上一篇
5分钟从零构建第一个 Apache Flink 应用
在本文中,我们将从零开始,教您如何构建第一个Apache Flink (以下简称Flink)应用程序。 开发环境准备 Flink 可以运行在 Linux, Max OS X, 或者是 Windows 上。为了开发 Flink 应用程序,在本地机器上需要有Java 8.x和maven环境。 如果有 Java 8 环境,运行下面的命令会输出如下版本信息: $ java -version java version "1.8.0_65" Java(TM) SE Runtime Environment (build 1.8.0_65-b17) Java HotSpot(TM) 64-Bit Server VM (build 25.65-b01, mixed mode) 如果有 maven 环境,运行下面的命令会输出如下版本信息: $ mvn -version Apache Maven 3.5.4 (1edded0938998edf8bf061f1ceb3cfdeccf443fe; 2018-06-18T02:33:14+08:00) Maven home: /Users/wuchong...
- 下一篇
案例分享:巧用工具提升无源码系统的性能和稳定
导读:在没有核心系统源码的情况下,修改源码打印耗时的方法无法使用,通过tcpdump、wireshark、gdb、010 editor、火焰图、ida、数据库抓sql耗时语句、oracle ash报告、loadrunner等工具找到了服务器tps上不去、C程序进程随机挂掉的问题,并顺利解决,收获颇多。 背景 公司最近新上线一个系统,主要架构如下: 测试环境系统部署后,出现了两个问题: 1.loadrunner压测tps上不去,压测java接口tps 单机只能到100多tps就上不去了,耗时从单次访问的100ms上升到110并发时的1s左右。 2. 压测期间C服务器1 经常不定时挂掉。 因为某些原因,该项目C相关程序没有源码,只有安装部署文件,为了解决上述两个问题,我们几个同事和重庆同事一块参与问题排查和解决。因为没有源码,中间经历了层层波折,经过一个月努力,终于解决了上述两个问题,整个排查过程学到了很多知识。 用到的分析工具 1.tcpdump, 2.wireshark, 3.gdb, 4.010 editor, 5.火焰图, 6.ida, 7.数据库抓sql耗时语句, 8.oracl...
相关文章
文章评论
共有0条评论来说两句吧...
文章二维码
点击排行
推荐阅读
最新文章
- Windows10,CentOS7,CentOS8安装MongoDB4.0.16
- CentOS8编译安装MySQL8.0.19
- MySQL8.0.19开启GTID主从同步CentOS8
- CentOS8安装Docker,最新的服务器搭配容器使用
- CentOS8,CentOS7,CentOS6编译安装Redis5.0.7
- SpringBoot2整合Redis,开启缓存,提高访问速度
- CentOS7,8上快速安装Gitea,搭建Git服务器
- Docker使用Oracle官方镜像安装(12C,18C,19C)
- CentOS关闭SELinux安全模块
- SpringBoot2初体验,简单认识spring boot2并且搭建基础工程