大一的时候学校就开了C语言这门课,最开始糊里糊涂无从下手,后来慢慢写了几个几百行的小程序就逐渐明白编程是怎么一回事了,还以为自己都懂了(too young 啊),可是后来蹭了一节java公选课,才知道还有面向对象编程这么一回事。老师说C是面向过程的,代码超过十万行就不好组织管理了,还得要面向对象的语言才能解决这个问题。我当时仿佛发现了一个新大陆,于是就开启了自学java面向对象编程的路程。
我感觉这个问题还真的不好回答,不同的人有不同的理解,下面就谈谈我的理解,望大家指正。
要说面向对象首先就得说说面向过程(出现的先后顺序),面向过程就是分析要解决的问题A,然后把这个问题 划分 为不同的步骤A1,A2,...(或者问题足够小就不用划分)。然后用函数把这些子问题一个一个地实现,最终解决A。
而面向对象是把问题中出现的角色 独立 出来,让他们互相通信来完成最终的问题。
不管是面向过程还是面向对象,都是我们认识世界的一种方法。那你可能会问了,既然面向过程先出现而且能解决问题,那么面向对象为什么会出现呢?首先面向过程既然出现就肯定有它的道理,因为它符合我们最常见的逻辑,我们现实生活中遇上问题大多会采用这种思维,比如我有个目标A:“我要成为科学家” ,那么很自然我就会想到A1:完成小学,A2:完成中学,A3:完成大学,A4:完成研究生,...,An:获得科研机构认可。把这一系列子问题挨个解决,那么我的问题就解决了,如图:
代码:
A(baby){ primary = A1(baby); middle = A2(primary); university = A3(middle); graduate = A4(university); //... scientist = An(graduate); return scientist; }
这是一种很自然的思路,所以面向过程最早出现。但是这种思路有个问题就是当系统庞大了庞大起来过后,各个步骤之间协调起来比较复杂,修改一个步骤可能引起很多的步骤的改变,比如那一天中国在中学和大学之间增加了一个学历preUniversity,那么至少就得改动两个函数,如果美国没有小学那么这个函数就得搞一个美国版去掉A1,修改A2。等等等等。
这既不利于维护,也没有达到代码可重用的目的。面向对象就应运而生了,用面向对象的思路就可以如下解决:
代码:
//人 class People(){ } //教育机构 class Education(People people){ private People people = people; public void primary(People people); public void middle (People people); public void university (People people); public void graduate (People people); //... public void scientist (People people); public bool finish(){ primary(people); middle (people); university (people); graduate (people); scientist (people); retrun true; } } //科研机构 class Institution(){ public void Recognize(Education education){ if(education.finish()&&others){//完成学业以及科研机构的其他判断条件 print "Scientist"; }else{ print "Not Scientist"; } } public static void main(Sting args[]){ People people = new People(); Education education = new Education(people); Recognize(education); } }
这样的话,即使学习历程有变化都可以只在Education类中修改,不同国别学历不同也可以继承这个基类来实现,大大提高了维护性和重用性。
概念上来说,如果一个问题反复发生,那么这个问题的解决方案就会被反复的使用,这种被频繁使用的解决方案就是模式。设计模式是语言独立的,主要用来解决程序设计的一般性问题(简单说来就是组织代码)。比如我们做菜,无论是西红柿炒鸡蛋还是红烧排骨,做多了就发现这个行为是有着固定模式的:点火,切菜,入锅,装进盘子。于是乎炒菜便成为了一种模式,以后每种菜(菜品独立)都可以沿袭这种模式,然后不同的菜实施细节不同而已。
按我理解,设计模式存在的终极意义就是代码的可重用性(也就顺带了可维护性)。其实不只是设计模式,面向过程和面向对象本身其实也有这个作用,只不过面向过程是通过函数来实现,而面向对象是通过类来实现,而且面向对象把这个目的完成的更好一些。更彻底一些。
设计模式一般来说包含如下几个方面:
模式名称,设计模式的名称是重要的,因为它会让其他程序员能立刻理解你的代码的目的
问题陈述,问题描述是用来说明这个模式的应用的领域,即应该在何时使用该模式
解决方案,解决方案描述了这个模型的执行
效果,描述了模式应用的效果及使用模式应权衡的问题
下面我就通过一些常见的设计模式来说明设计模式是怎样起到这种效果的。
在面向对象编程中, 最通常的方法是一个 new 操作符产生一个对象实例,new 操作符就是用来构造对象实例的。但是在一些情况下 , new操作符直接生成对象会带来一些问题。举例来说, 许多类型对象的创造需要一系列的步骤: 你可能需要计算或取得对象的初始设置 ;选择生成哪个子对象实例 ; 或在生成你需要的对象之前必须先生成一些辅助功能的对象。在这些情况, 新对象的建立就是一个 “过程” ,不仅是一个操作,像一部大机器中的一个齿轮传动。你如何能轻松方便地建立这么" 复杂 " 的对象即操作中不需要粘贴复制呢?
定义一个用于创建对象的接口,让子类决定实例化哪一个类。工厂方法使一个类的实例化延迟到其子类。
工厂方法模式通用类图:
在工厂方法模式中,抽象产品类Product负责定义产品的共性,实现对事物最抽象的定义;Creator为抽象创建类,也就是抽象工厂,具体如何创建产品类是由具体的实现工厂ConcreteCreator完成的。我们来看一个比较实用的通用源码:
//抽象产品类 public abstract class Product { //产品类的公共方法 public void method1(){ //业务逻辑处理 } //抽象方法 public abstract void method2(); } //具体产品类,可以有多个 public class ConcreteProduct1 extends Product { public void method2() { //业务逻辑处理 } } public class ConcreteProduct2 extends Product { public void method2() { //业务逻辑处理 } } //抽象工厂类 public abstract class Creator { //创建一个产品对象,其输入参数类型可以自行设置 //通常为String、Enum、Class等,当然也可以为空 public abstract <T extends Product> T createProduct(Class<T> c); } //具体工厂类 public class ConcreteCreator extends Creator { public <T extends Product> T createProduct(Class<T> c){ Product product=null; try { product = (Product)Class.forName(c.getName()).newInstance(); } catch (Exception e) { //异常处理 } return (T)product; } } //场景类 public class Client { public static void main(String[] args) { Creator creator = new ConcreteCreator(); Product product = creator.createProduct(ConcreteProduct1.class); //继续业务处理 } }
首先,代码具有了良好的封装性,结构清晰。一个对象创建是有条件约束的,一个调用者需要一个具体的产品对象,只要知道这个产品的类名(或约束字符串)就可以了,不用知道创建对象的艰辛过程,降低模块间的耦合。
其次,工厂方法模式的扩展性非常好。在增加产品类的情况下,只要适当地修改具体的工厂类或扩展一个工厂类就行了。
再次,屏蔽产品类。这一特点非常重要,产品类的实现如何变化,调用者都不需要关心,它只需要关心产品的接口,只要接口保持不变,系统中的上层模块就不要发生变化。因为产品类的实例化工作是由工厂类负责的,一个产品对象具体由哪一个产品生成是由工厂类决定的。在数据库开发中,大家应该能够深刻体会到工厂方法模式的好处:如果使用JDBC连接数据库,数据库从My SQL 切换到Oracle,需要改动的地方就是切换一下驱动名称(前提条件是SQL 语句是标准语句),其他的都不需要修改,这是工厂方法模式灵活性的一个直接案例。
最后,工厂方法模式是典型的解耦框架。
如果一个事件A发生了需要触发另一个事件B之时该怎么办呢?直接修改A?显然不是科学的程序设计,因为这样就达不到A,B解耦合的目的,如果要修改B就得直接修改A;如果A同时还要触发事件C怎么办?又继续往A里面添加?显然这样做最后会导致A臃肿不堪难以维护,这个时候就需要用到观察者模式了。
定义对象间一种一对多的依赖关系,使得每当一个对象改变状态,则所有依赖于它的对象都会得到通知并被自动更新。
观察者模式通用类图:
Subject被观察者,定义被观察者必须实现的职责,它必须能够动态地增加、取消观察者。它一般是抽象类或者是实现类,仅仅完成作为被观察者必须实现的职责:管理观察者并通知观察者。
Observer观察者,观察者接收到消息后,即进行update(更新方法)操作,对接收到的信息进行处理。
ConcreteSubject具体的被观察者,定义被观察者自己的业务逻辑,同时定义对哪些事件进行通知。
ConcreteObserver具体的观察者,每个观察在接收到消息后的处理反应是不同,各个观察者有自己的处理逻辑。
通用代码如下:
//被观察者 public abstract class Subject { //定义一个观察者数组 private Vector<Observer> obsVector = new Vector<Observer>(); //增加一个观察者 public void addObserver(Observer o){ this.obsVector.add(o); } //删除一个观察者 public void delObserver(Observer o){ this.obsVector.remove(o); } //通知所有观察者 public void notifyObservers(){ for(Observer o:this.obsVector){ o.update(); } } } //具体被观察者 public class ConcreteSubject extends Subject { //具体的业务 public void doSomething(){ //do something super.notifyObservers(); } } //观察者 public interface Observer { //更新方法 public void update(); } //具体观察者 public class ConcreteObserver implements Observer { //实现更新方法 public void update() { System.out.println("接收到信息,并进行处理!"); } } //场景类 public class Client { public static void main(String[] args) { //创建一个被观察者 ConcreteSubject subject = new ConcreteSubject(); //定义一个观察者 Observer obs= new ConcreteObserver(); //观察者观察被观察者 subject.addObserver(obs); //观察者开始活动了 subject.doSomething(); } }
首先,观察者和被观察者之间是抽象耦合。如此设计,则不管是增加观察者还是被观察者都非常容易扩展,而且在Java中都已经实现的抽象层级的定义,在系统扩展方面更是得心应手。
其次,建立了一套触发机制根据 单一职责原则 ,每个类的职责是单一的,那么怎么把各个单一的职责串联成真实世
界的复杂的逻辑关系呢?比如,我们去打猎,打死了一只母鹿,母鹿有三个幼崽,因失去了母鹿而饿死,尸体又被两只秃鹰争抢,因分配不均,秃鹰开始斗殴,然后羸弱的秃鹰死掉,生存下来的秃鹰,则因此扩大了地盘……这就是一个触发机制,形成了一个触发链。观察者模式可以完美地实现这里的链条形式。
如果你有很多的算法,他们只是有些许不同,你需要在不同场景使用不同的算法,这个时候是不是需要很多的if语句来判断呢?可是if语句写多了又不好维护;
如果你的算法规则不希望被别人看见,该怎么办;
这一切都交给策略模式吧。
定义一组算法,将每个算法都封装起来,并且使它们之间可以互换。
策略模式的通用类图:
Context 封装角色,它也叫做上下文角色,起承上启下封装作用,屏蔽高层模块对策略、算法的直接访问,封装可能存在的变化。
Strategy 抽象策略角色,策略、算法家族的抽象,通常为接口,定义每个策略或算法必须具有的方法和属性。
ConcreteStrategy 具体策略角色,实现抽象策略中的操作,该类含有具体的算法。
通用代码:
//抽象的策略角色 public interface Strategy { //策略模式的运算法则 public void doSomething(); } //具体策略角色 public class ConcreteStrategy1 implements Strategy { public void doSomething() { System.out.println("具体策略1的运算法则"); } } public class ConcreteStrategy2 implements Strategy { public void doSomething() { System.out.println("具体策略2的运算法则"); } } //封装角色 public class Context { //抽象策略 private Strategy strategy = null; //构造函数设置具体策略 public Context(Strategy _strategy){ this.strategy = _strategy; } //封装后的策略方法 public void doAnythinig(){ this.strategy.doSomething(); } } //场景类 public class Client { public static void main(String[] args) { //声明一个具体的策略 Strategy strategy = new ConcreteStrategy1(); //声明上下文对象 Context context = new Context(strategy); //执行封装后的方法 context.doAnythinig(); } }
首先,算法可以自由切换。这是策略模式本身定义的,只要实现抽象策略,它就成为策略家族的一个成员,通过封装角色对其进行封装,保证对外提供“可自由切换”的策略。
其次,避免使用多重条件判断。如果没有策略模式,我们想想看会是什么样子?一个策略家族有5个策略算法,一会要使用A策略,一会要使用B策略,怎么设计呢?使用多重的条件语句?多重条件语句不易维护,而且出错的概率大大增强。使用策略模式后,可以由其他模块决定采用何种策略,策略家族对外提供的访问接口就是封装类,简化了操作,同时避免了条件语句判断。
最后,扩展性良好。在现有的系统中增加一个策略太容易了,只要实现接口就可以了,其他都不用修改,类似于一个可反复拆卸的插件,这大大地符合了 OCP原则 。
还有很多设计模式我就不一一阐述了。大家可以找找资料看看。
我感觉设计模式说白了就是对面向对象编程的一种补充,把面向对象方法所希望达到的效果进一步完善罢了。但是这些模式还得在实战中慢慢体会,以后编程过程中慢慢加深运用设计模式,相信会对我们写出可复用、易维护的代码有帮助的。
本文参考了《大话设计模式》,这本书通俗易懂,风趣幽默,强烈推荐给大家~