计算机软件技术发展相当迅速,各种框架和技术层出不穷,每一家有有规模的大公司都有自己的一套解决方案,将其提炼出来就是一套新技术,例如React Native就是衍生自Facebook内部项目。面临如此多而且演变迅速的技术,我们经常不知从何入手,可能刚学会了一种技术,该技术就被淘汰了,这样我们总是难以追上互联网高速的脚步。我曾经在各种技术之间学花了眼,学了java后又觉得C#不错,学了servlet觉得PHP用的挺广泛,然后又看到了Nodejs是新技术必须学..这样整天在各种技术上切换,都只学会了点皮毛,没有娴熟的使用一种技术解决问题的能力。最终我意识到,作为一个程序员,修炼好内功才是比追赶时髦技术更重要的事情。找了几本锻炼编程思维的书《大话设计模式》、《程序员思维修炼》、《重构-改变现有代码设计》,每天坚持看一点,希望能把内功修炼起来,这样才能从思维高度认识到千变万化的新技术。那么,从面向对象开始,记录学习《大话设计模式》的心得。
接触到的第一门编程语言是C,从C语言了解到高级语言的一些特性,但是C语言是一种面向过程的语言,这也就导致了从一开始学习编程就养成了面向过程的变成习惯,这种习惯根深蒂固以至于总是用面向过程的思维去解决问题。但是面向过程的思想只适用于解决一些小型问题,例如描述一个算法,完成一个小功能等,而面对大型项目,面向过程自顶向下的编程会使问题便的复杂,为什么这么说呢,例如要写一个计算器的程序,完成计算功能,写成java代码可能是这样的:
public class Test {
public static void main(String[] args) {
String op = "" ;
int op1 = 1 ;
int op2 = 2 ;
switch (op) {
case "+":
System.out.println(op1+op2);
break;
case "-":
System.out.println(op1-op2);
break;
case "*":
System.out.println(op1*op2);
break;
case "/":
try {
System.out.println(op1/op2);
} catch (Exception e) {
throw new RuntimeException("Divided by zero") ;
}
break;
default:
break;
}
}
}
虽然使用面向对象的java语言,但是这段代码是典型的面向过程的逻辑。因为这段代码只考虑解决问题,没有考虑维护和扩展,例如当程序需要增加一个计算幂次方的功能,那么此时必须去改动程序源代码,然后再重新编译一次,当这个项目非常大,且模块互相关联比较复杂时,一个小小的改动可能会引起未知的bug,并且修改了源代码之后还要将整个系统重新编译一次,这样算下来,只是仅仅的增加一个小功能就要承担如此大的代价,显然这样的的代码在项目中就是不合格的。因此,能够运行的代码不一定就是好代码,面向对象的技术就解决面向过程编程中的不足。面向对象通常有四个好处:易维护、可扩展、可复用、灵活性高。
怎样理解面向对象带来的这些好处呢?这引用《大话设计模式》中的例子:中国四大发明中活字印刷术是一个特例,因为他实际上是一种思想上的创新。想当初雕版印刷术是一种非常麻烦的技术,每次印刷都要将印刷的内容整个刻在版上,要印刷不同的内容就必须重新制作雕版,那么问题来了,如果不小心将雕版中某个字印错了,或者只需要修改雕版中某个字,其他字不变时,都需要废弃掉重新制作整个版面,这样耗费的人力物力就大 了(这就相当于面向过程编程中将系统功能按一定流程逐个实现一样,从外部看就是一个整体,当需求变更时,都需要将原有系统重做)。活字印刷术的出现很好的解决了这个问题,它以单个字为单位,需要印什么样的内容就将相应的字组合成一个版面进行印刷,需要修改是只需要将相应字从版面中替换掉,这叫可修改;不同字在印刷完后还可以组合成其他版面,这叫可复用;需要对增加一些内容时,并不需要去连之前的雕版都一起重做,只需要用单个字的印模组成新的内容即可,这叫可扩展;各个字的印模可以横向排列也可以竖向排列,形成不同的排版,这叫灵活性高。所以活字印刷术可以说是古人对面向对象思想的一种实践,他们不再将印刷整内容作为目的而做雕版,而是将任务分解,将单个字看成对象,用字这个对象的属性和行为来完成印刷。
那么,根据面向对象的思想来修改上面代码:
package com.hk.triangle;
abstract class Operation{
abstract public int getReslut(int op1,int op2);
}
class Add extends Operation{
@Override
public int getReslut(int op1, int op2) {
return op1+op2;
}
}
class Sub extends Operation{
@Override
public int getReslut(int op1, int op2) {
return op1 - op2;
}
}
class Multi extends Operation{
@Override
public int getReslut(int op1, int op2) {
return op1*op2;
}
}
class Divide extends Operation{
@Override
public int getReslut(int op1, int op2) {
try {
return op1/op2;
} catch (Exception e) {
throw new RuntimeException("Divided by zero") ;
}
}
}
public class Test {
public static void main(String[] args) {
String op = "+" ;
int op1 = 1 ;
int op2 = 2 ;
Operation operation = null ;
switch (op) {
case "+":
operation = new Add() ;
System.out.println(operation.getReslut(op1, op2));
break;
case "-":
operation = new Sub() ;
System.out.println(operation.getReslut(op1, op2));
break;
case "*":
operation = new Multi() ;
System.out.println(operation.getReslut(op1,op2));
break;
case "/":
try {
operation = new Divide() ;
System.out.println(operation.getReslut(op1, op2));
} catch (Exception e) {
throw new RuntimeException("Divided by zero") ;
}
break;
default:
break;
}
}
}
按照面向对象的方式改写代码之后,如果需要添加新的幂次方功能,只需要写一个处理幂次方的类,然后添加到客户端switch分支中即可(可扩展),我们将各个运算封装在各自的类中,因此添加新的运算并不会影响其他的运算,这里改写之后的代码变的更复杂,但是这样更易于复用(能被其他类引用)、可维护(修改隔离)并且更灵活(组合起来完成一些组合运算)。
当然,面向对象还涉及很多的规范和模式,例如代码中生成Operation运算实例的时候在客户端代码中进行了switch判断,这样做事很不优雅的,因为我们需要了解四个类的作用才能正确new出实例,简单工厂设计模式可以使我们在生成实例的时候,不需要知道是谁new出了这个实例,使代码更加优雅,更符合面向对象的设计规范。总之,面向对象博大精深,需要在时间和实战中慢慢渗透。