C# 多态的实现
封装、继承、多态,面向对象的三大特性,前两项理解相对容易,但要理解多态,特别是深刻的了解,对于初学者而言可能就会有必定困难了。我一直认为学习OO的最好方法就是结合实践,封装、继承在实际工做中的应用随处可见,但多态呢?也许未必,可能不经意间用到也不会把它跟“多态”这个词对应起来。在此抛砖引玉,你们讨论,我的能力有限,不足之处还请指正。
以前看到过相似的问题:若是面试时主考官要求你用一句话来描述多态,尽量的精炼,你会怎么回答?固然答案有不少,每一个人的理解和表达不尽相同,但我比较趋向这样描述:经过继承实现的不一样对象调用相同的方法,表现出不一样的行为,称之为多态。html
例1:java
public class Animal { public virtual void Eat() { Console.WriteLine("Animal eat"); } } public class Cat : Animal { public override void Eat() { Console.WriteLine("Cat eat"); } } public class Dog : Animal { public override void Eat() { Console.WriteLine("Dog eat"); } } class Tester { static void Main(string[] args) { Animal[] animals = new Animal[3]; animals[0] = new Animal(); animals[1] = new Cat(); animals[2] = new Dog(); for (int i = 0; i < 3; i++) { animals[i].Eat(); } } }
输出以下:程序员
Animal eat...面试
Cat eat...算法
Dog eat...编程
在上面的例子中,经过继承,使得Animal对象数组中的不一样的对象,在调用Eat()方法时,表现出了不一样的行为。c#
多态的实现看起来很简单,要彻底理解及灵活的运用c#的多态机制,也不是一件容易的事,有不少须要注意的地方。设计模式
1. new的用法数组
先看下面的例子。微信
例2:
public class Animal { public virtual void Eat() { Console.WriteLine("Animal eat"); } } public class Cat : Animal { public new void Eat() { Console.WriteLine("Cat eat"); } } class Tester { static void Main(string[] args) { Animal a = new Animal(); a.Eat(); Animal ac = new Cat(); ac.Eat(); Cat c = new Cat(); c.Eat(); } }
运行结果为:
Animal eat...
Animal eat...
Cat eat...
能够看出,当派生类Cat的Eat()方法使用new修饰时,Cat的对象转换为Animal对象后,调用的是Animal类中的Eat()方法。其实能够理解为,使用new关键字后,使得Cat中的Eat()方法和Animal中的Eat()方法成为绝不相关的两个方法,只是它们的名字碰巧相同而已。因此, Animal类中的Eat()方法无论用仍是不用virtual修饰,也无论访问权限如何,或者是没有,都不会对Cat的Eat()方法产生什么影响(只是由于使用了new关键字,若是Cat类没用从Animal类继承Eat()方法,编译器会输出警告)。
我想这是设计者有意这么设计的,由于有时候咱们就是要达到这种效果。严格的说,不能说经过使用new来实现多态,只能说在某些特定的时候碰巧实现了多态的效果。
2.override实现多态
真正的多态使用override来实现的。回过去看前面的例1,在基类Animal中将方法Eat()用virtual标记为虚拟方法,再在派生类Cat和Dog中用override对Eat()修饰,进行重写,很简单就实现了多态。须要注意的是,要对一个类中一个方法用override修饰,该类必须从父类中继承了一个对应的用virtual修饰的虚拟方法,不然编译器将报错。
好像讲得差很少了,还有一个问题,不知道你想没有。就是多层继承中又是怎样实现多态的。好比类A是基类,有一个虚拟方法method()(virtual修饰),类B继承自类A,并对method()进行重写(override修饰),如今类C又继承自类B,是否是能够继续对method()进行重写,并实现多态呢?看下面的例子。
例3:
public class Animal { public virtual void Eat() { Console.WriteLine("Animal eat"); } } public class Dog : Animal { public override void Eat() { Console.WriteLine("Dog eat"); } } public class WolfDog : Dog { public override void Eat() { Console.WriteLine("WolfDog eat"); } } class Tester { static void Main(string[] args) { Animal[] animals = new Animal[3]; animals[0] = new Animal(); animals[1] = new Dog(); animals[2] = new WolfDog(); for (int i = 0; i < 3; i++) { animals[i].Eat(); } } }
运行结果为:
Animal eat...
Dog eat...
WolfDog eat...
在上面的例子中类Dog继承自类Animal,对方法Eat()进行了重写,类WolfDog又继承自Dog,再一次对Eat()方法进行了重写,并很好地实现了多态。无论继承了多少层,均可以在子类中对父类中已经重写的方法继续进行重写,即若是父类方法用override修饰,若是子类继承了该方法,也能够用override修饰,多层继承中的多态就是这样实现的。要想终止这种重写,只需重写方法时用sealed关键字进行修饰便可。
3. abstract-override实现多态
先在咱们在来讨论一下用abstract修饰的抽象方法。抽象方法只是对方法进行了定义,而没有实现,若是一个类包含了抽象方法,那么该类也必须用abstract声明为抽象类,一个抽象类是不能被实例化的。对于类中的抽象方法,能够再其派生类中用override进行重写,若是不重写,其派生类也要被声明为抽象类。看下面的例子。
例4:
public abstract class Animal { public abstract void Eat(); } public class Cat : Animal { public override void Eat() { Console.WriteLine("Cat eat"); } } public class Dog : Animal { public override void Eat() { Console.WriteLine("Dog eat"); } } public class WolfDog : Dog { public override void Eat() { Console.WriteLine("Wolfdog eat"); } } class Tester { static void Main(string[] args) { Animal[] animals = new Animal[3]; animals[0] = new Cat(); animals[1] = new Dog(); animals[2] = new WolfDog(); for (int i = 0; i < animals.Length; i++) { animals[i].Eat(); } } }
运行结果为:
Cat eat...
Dog eat...
Wolfdog eat...
从上面能够看出,经过使用abstract-override能够和virtual-override同样地实现多态,包括多层继承也是同样的。不一样之处在于,包含虚拟方法的类能够被实例化,而包含抽象方法的类不能被实例化。
前言:咱们都知道面向对象的三大特性:封装,继承,多态。封装和继承对于初学者而言比较好理解,但要理解多态,尤为是深刻理解,初学者每每存在有不少困惑,为何这样就能够?有时候感受很难以想象,由此,面向对象的魅力体现了出来,那就是多态,多态用的好,能够提升程序的扩展性。经常使用的设计模式,好比简单工厂设计模式,核心就是多态。
其实多态就是:容许将子类类型的指针赋值给父类类型的指针。也就是同一操做做用于不一样的对象,能够有不一样的解释,产生不一样的执行结果。在运行时,能够经过指向基类的指针,来调用实现派生类中的方法。若是这边不理解能够先放一放,先看下面的事例,看完以后再来理解这句话,就很容易懂了。
理解多态以前首先要对面向对象的里氏替换原则和开放封闭原则有所了解。
里氏替换原则(Liskov Substitution Principle):派生类(子类)对象可以替换其基类(超类)对象被使用。通俗一点的理解就是“子类是父类”,举个例子,“男人是人,人不必定是男人”,当须要一个父类类型的对象的时候能够给一个子类类型的对象;当须要一个子类类型对象的时候给一个父类类型对象是不能够的!
开放封闭原则(Open Closed Principle):封装变化、下降耦合,软件实体应该是可扩展,而不可修改的。也就是说,对扩展是开放的,而对修改是封闭的。所以,开放封闭原则主要体如今两个方面:对扩展开放,意味着有新的需求或变化时,能够对现有代码进行扩展,以适应新的状况。对修改封闭,意味着类一旦设计完成,就能够独立完成其工做,而不要对类进行任何修改。
对这两个原则有必定了解以后就能更好的理解多态。
咱们都知道,喜鹊(Magpie)、老鹰(Eagle)、企鹅(Penguin)都是属于鸟类,咱们能够根据这三者的共有特性提取出鸟类(Bird)作为父类,喜鹊喜欢吃虫子,老鹰喜欢吃肉,企鹅喜欢吃鱼。
建立基类Bird以下,添加一个虚方法Eat():
/// <summary> /// 鸟类:父类 /// </summary> public class Bird { /// <summary> /// 吃:虚方法 /// </summary> public virtual void Eat() { Console.WriteLine("我是一只小小鸟,我喜欢吃虫子~"); } }
建立子类Magpie以下,继承父类Bird,重写父类Bird中的虚方法Eat():
/// <summary> /// 喜鹊:子类 /// </summary> public class Magpie:Bird { /// <summary> /// 重写父类中Eat方法 /// </summary> public override void Eat() { Console.WriteLine("我是一只喜鹊,我喜欢吃虫子~"); } }
建立一个子类Eagle以下,继承父类Bird,重写父类Bird中的虚方法Eat():
/// <summary> /// 老鹰:子类 /// </summary> public class Eagle:Bird { /// <summary> /// 重写父类中Eat方法 /// </summary> public override void Eat() { Console.WriteLine("我是一只老鹰,我喜欢吃肉~"); } }
建立一个子类Penguin以下,继承父类Bird,重写父类Bird中的虚方法Eat():
/// <summary> /// 企鹅:子类 /// </summary> public class Penguin:Bird { /// <summary> /// 重写父类中Eat方法 /// </summary> public override void Eat() { Console.WriteLine("我是一只小企鹅,我喜欢吃鱼~"); } }
到此,一个基类,三个子类已经建立完毕,接下来咱们在主函数中来看下多态是怎样体现的。
static void Main(string[] args) { //建立一个Bird基类数组,添加基类Bird对象,Magpie对象,Eagle对象,Penguin对象 Bird[] birds = { new Bird(), new Magpie(), new Eagle(), new Penguin() }; //遍历一下birds数组 foreach (Bird bird in birds) { bird.Eat(); } Console.ReadKey(); }
运行结果:
因而可知,子类Magpie,Eagle,Penguin对象能够赋值给父类对象,也就是说父类类型指针能够指向子类类型对象,这里体现了里氏替换原则。
父类对象调用本身的Eat()方法,实际上显示的是父类类型指针指向的子类类型对象重写父类Eat后的方法。这就是多态。
多态的做用究竟是什么呢?
其实多态的做用就是把不一样的子类对象都看成父类来看,能够屏蔽不一样子类对象之间的差别,写出通用的代码,作出通用的编程,以适应需求的不断变化。
以上程序也体现了开放封闭原则,若是后面的同事须要扩展我这个程序,还想再添加一个猫头鹰(Owl),很容易,只须要添加一个Owl类文件,继承Bird,重写Eat()方法,添加给父类对象就能够了。至此,该程序的扩展性获得了提高,而又不须要查看源代码是如何实现的就能够扩展新功能。这就是多态带来的好处。
仍是刚才的例子,咱们发现Bird这个父类,咱们根本不须要使用它建立的对象,它存在的意义就是供子类来继承。因此咱们能够用抽象类来优化它。
咱们把Bird父类改为抽象类,Eat()方法改为抽象方法。代码以下:
/// <summary> /// 鸟类:基类 /// </summary> public abstract class Bird { /// <summary> /// 吃:抽象方法 /// </summary> public abstract void Eat(); }
抽象类Bird内添加一个Eat()抽象方法,没有方法体。也不能实例化。
其余类Magpie,Eagle,Penguin代码不变,子类也是用override关键字来重写父类中抽象方法。
Main主函数中Bird就不能建立对象了,代码稍微修改以下:
static void Main(string[] args) { //建立一个Bird基类数组,添加 Magpie对象,Eagle对象,Penguin对象 Bird[] birds = { new Magpie(), new Eagle(), new Penguin() }; //遍历一下birds数组 foreach (Bird bird in birds) { bird.Eat(); } Console.ReadKey(); }
执行结果:
因而可知,咱们选择使用虚方法实现多态仍是抽象类抽象方法实现多态,取决于咱们是否须要使用基类实例化的对象.
好比说 如今有一个Employee类做为基类,ProjectManager类继承自Employee,这个时候咱们就须要使用虚方法来实现多态了,由于咱们要使用Employee建立的对象,这些对象就是普通员工对象。
再好比说 如今有一个Person类做为基类,Student,Teacher 类继承Person,咱们须要使用的是Student和Teacher建立的对象,根本不须要使用Person建立的对象,
因此在这里Person彻底能够写成抽象类。
总而言之,是使用虚方法,或者抽象类抽象方法实现多态,视状况而定,什么状况?以上我说的两点~
接下来~~~~
我要问一个问题,喜鹊和老鹰均可以飞,这个飞的能力,我怎么来实现呢?
XXX答:“在父类Bird中添加一个Fly方法不就行了~~”
我再问:“好的,照你说的,企鹅继承父类Bird,可是不能企鹅不能飞啊,这样在父类Bird中添加Fly方法是否是不合适呢?”
XXX答:“那就在能飞的鸟类中分别添加Fly方法不就能够了吗?”
对,这样是能够,功能彻底能够实现,但是这样违背了面向对象开放封闭原则,下次我要再扩展一个鸟类好比猫头鹰(Owl),我还要去源代码中看下Fly是怎么实现的,而后在Owl中再次添加Fly方法,相同的功能,重复的代码,这样是不合理的,程序也不便于扩展;
其次,若是我还要添加一个飞机类(Plane),我继承Bird父类,合适吗?
很显然,不合适!因此咱们须要一种规则,那就是接口了,喜鹊,老鹰,飞机,我都实现这个接口,那就能够飞了,而企鹅我不实现这个接口,它就不能飞~~
添加一个接口IFlyable,代码以下:
/// <summary> /// 飞 接口 /// </summary> public interface IFlyable { void Fly(); }
喜鹊Magpie实现IFlyable接口,代码以下:
/// <summary> /// 喜鹊:子类,实现IFlyable接口 /// </summary> public class Magpie:Bird,IFlyable { /// <summary> /// 重写父类Bird中Eat方法 /// </summary> public override void Eat() { Console.WriteLine("我是一只喜鹊,我喜欢吃虫子~"); } /// <summary> /// 实现 IFlyable接口方法 /// </summary> public void Fly() { Console.WriteLine("我是一只喜鹊,我能够飞哦~~"); } }
老鹰Eagle实现IFlyable接口,代码以下:
/// <summary> /// 老鹰:子类实现飞接口 /// </summary> public class Eagle:Bird,IFlyable { /// <summary> /// 重写父类Bird中Eat方法 /// </summary> public override void Eat() { Console.WriteLine("我是一只老鹰,我喜欢吃肉~"); } /// <summary> /// 实现 IFlyable接口方法 /// </summary> public void Fly() { Console.WriteLine("我是一只老鹰,我能够飞哦~~"); } }
在Main主函数中,建立一个IFlyable接口数组,代码实现以下:
static void Main(string[] args) { //建立一个IFlyable接口数组,添加 Magpie对象,Eagle对象 IFlyable[] flys = { new Magpie(), new Eagle() }; //遍历一下flys数组 foreach (IFlyable fly in flys) { fly.Fly(); } Console.ReadKey(); }
执行结果:
因为企鹅Penguin没有实现IFlyable接口,因此企鹅不能对象不能赋值给IFlyable接口对象,因此企鹅,不能飞~
好了,刚才我提到了飞机也能飞,继承Bird不合适的问题,如今有了接口,这个问题也能够解决了。以下,我添加一个飞机Plane类,实现IFlyable接口,代码以下:
/// <summary> /// 飞机类,实现IFlyable接口 /// </summary> public class Plane:IFlyable { /// <summary> /// 实现接口方法 /// </summary> public void Fly() { Console.WriteLine("我是一架飞机,我也能飞~~"); } }
在Main主函数中,接口IFlyable数组,添加Plane对象:
class Program { static void Main(string[] args) { //建立一个IFlyable接口数组,添加 Magpie对象,Eagle对象,Plane对象 IFlyable[] flys = { new Magpie(), new Eagle(), new Plane() }; //遍历一下flys数组 foreach (IFlyable fly in flys) { fly.Fly(); } Console.ReadKey(); } }
执行结果:
由此,能够看出用接口实现多态程序的扩展性获得了大大提高,之后无论是再扩展一个蝴蝶(Butterfly),仍是鸟人(Birder)建立一个类,实现这个接口,在主函数中添加该对象就能够了。
也不须要查看源代码是如何实现的,体现了开放封闭原则!
接口充分体现了多态的魅力~~
以上经过一些小的事例,给你们介绍了面向对象中三种实现多态的方式,或许有人会问,在项目中怎么使用多态呢?多态的魅力在项目中如何体现?
那么接下来我作一个面向对象的简单计算器,来Show一下多态在项目中使用吧!
加减乘除运算,咱们能够根据共性提取出一个计算类,里面包含两个属性 Number1和Number2,还有一个抽象方法Compute();代码以下:
/// <summary> /// 计算父类 /// </summary> public abstract class Calculate { public int Number1 { get; set; } public int Number2 { get; set; } public abstract int Compute(); }
接下来,咱们添加一个加法器,继承计算Calculate父类:
/// <summary> /// 加法器 /// </summary> public class Addition : Calculate { /// <summary> /// 实现父类计算方法 /// </summary> /// <returns>加法计算结果</returns> public override int Compute() { return Number1 + Number2; } }
再添加一个减法器,继承计算Calculate父类:
/// <summary> /// 减法器 /// </summary> public class Subtraction : Calculate { /// <summary> /// 实现父类计算方法 /// </summary> /// <returns>减法计算结果</returns> public override int Compute() { return Number1 - Number2; } }
在主窗体FormMain中,编写计算事件btn_Compute_Click,代码以下:
private void btn_Compute_Click(object sender, EventArgs e) { //获取两个参数 int number1 = Convert.ToInt32(this.txt_Number1.Text.Trim()); int number2 = Convert.ToInt32(this.txt_Number2.Text.Trim()); //获取运算符 string operation = cbb_Operator.Text.Trim(); //经过运算符,返回父类类型 Calculate calculate = GetCalculateResult(operation); calculate.Number1 = number1; calculate.Number2 = number2; //利用多态,返回运算结果 string result = calculate.Compute().ToString(); this.lab_Result.Text = result; } /// <summary> /// 经过运算符,返回父类类型 /// </summary> /// <param name="operation"></param> /// <returns></returns> private Calculate GetCalculateResult(string operation) { Calculate calculate = null; switch (operation) { case "+": calculate = new Addition(); break; case "-": calculate = new Subtraction(); break; } return calculate; }
在该事件中主要调用GetCalculateResult方法,经过运算符,建立一个对应的加减乘除计算器子类,而后赋值给父类,其实这就是设计模式中的简单工厂设计模式,我给你一个运算符你给我生产一个对应的加减乘除计算器子类,返回给我。。其实大多数的设计模式的核心就是多态,掌握好多态,设计模式看起来也很轻松。
现阶段工做已经完成,可是过了一段时间,又添加新的需求了,我还要扩展一个乘法了,那好,很简单只要建立一个乘法计算器继承Calculate父类便可,看代码:
/// <summary> /// 乘法计算器 /// </summary> public class Multiplication:Calculate { public override int Compute() { return Number1*Number2; } }
而后在GetCalculateResult函数中添加一个case 就行了:
switch (operation) { case "+": calculate = new Addition(); break; case "-": calculate = new Subtraction(); break; case "*": calculate = new Multiplication(); break; }
执行结果:
好了,就这么方便,一个新的功能就扩展完毕了,我根本不须要查看源代码是如何实现的,这就是多态的好处!
多态性意味着有多重形式。在面向对象编程范式中,多态性每每表现为"一个接口,多个功能"。
多态性能够是静态的或动态的。在静态多态性中,函数的响应是在编译时发生的。在动态多态性中,函数的响应是在运行时发生的。
-------------------------------------------------------------------------------------------------
何时用接口何时用抽象?
抽象:是对相同属性和方法的提炼而得
接口:是对相同行为不一样实现方法的提炼
如: 每种支付方式 支付以前都须要校验一下支付金额是否是真确的,不能小于等于0 。由于校验方式,校验代码都是同样的,因此咱们能够定义一个 抽象类给抽象出来.
public abstract class AbstractPayWay implements PayWay{ private Double money; private boolean verify(){ return money != null && money > 0; } /** * 这里实现了 PayWay 中的 pay 接口 方法 因此 AbstractPayWay 的子类 无需 实现该 方法, * 只须要 实现 doPay() 方法,而且 若是 doPay()方法被成功调用则说明确定 校验成功了。 */ @Override public boolean pay(){ boolean verify = this.verify(); if(!verify){ System.out.println("支付金额验证错误!"); return false; } return this.doPay(); } public abstract boolean doPay(); }
因此 WeixinPayWay ZhifubaoPayWay 支付的具体实现类能够改改成
/** * 继承 AbstractPayWay 因此无需 写公共的校验逻辑,直接写支付业务逻辑 * @author cyy */ public class WeixinPayWay extends AbstractPayWay{ @Override public boolean doPay() { System.out.println("这里无需校验支付金额,直接调用支付方法就行"); System.out.println("微信支付成功"); return false; } }
public class ZhifubaoPayWay extends AbstractPayWay{ @Override public boolean doPay() { System.out.println("这里无需校验支付金额,直接调用支付方法就行"); System.out.println("支付宝支付成功"); return false; } }
参考:
https://blog.csdn.net/wab719591157/article/details/73741919
https://blog.csdn.net/u012135077/article/details/48286837
https://blog.csdn.net/u013538542/article/details/45365019
转:
java 中何时用抽象类,何时用 接口(面向对象的继承与多态)
抽象类:强调的是把共同(共有、相同)的属性方法, 抽象出来,统一写在一个地方(他们的实现代码是同样的),方便维护。(面向对象三大特性中的继承特性)
接口: 抽象的是行为 - 同一种行为的不一样实现方式。当多个对象都拥有相同的行为,可是行为的具体实现方式不同的时候能够用接口抽象(面向对象中的多态特性)
因此通常在实际项目中接口和抽象类是配合使用而不是相互替代
例如:
全部的订单都有单号,单价,数量。都拥有,并且相同,因此能够用一个抽象类给统一描述出来。
public abstract class AbstractOrder { private String serialNo; // 单号 private Double money; // 单价 private int number; // 数量 }
再有一个商品订单 还有一个独有的 商品名称 属性。因此 在新新建一个 ProductOrder 继承 AbstractOrder
public class ProductOrder extends AbstractOrder{ private String productName; }
另外 全部的订单都须要支付,可是支付方式又不同好比,微信支付,支付宝支付,同一种行为,可是具体的行为方式又不同。因此用一个接口给抽象出来(规定一个行为标准)
public interface PayWay { public boolean pay(); }
public class WeixinPayWay implements PayWay{
@Override public boolean pay() { System.out.println("微信支付成功"); return false; } }
public class ZhifubaoPayWay implements PayWay{ @Override public boolean pay() { System.out.println("支付宝支付成功"); return false; } }
由于全部订单都须要支付 因此 只须要 改造 AbstractOrder 类在里面增长一个 支付行为
public abstract class AbstractOrder { private String serialNo; // 单号 private Double money; // 单价 private int number; // 数量 private PayWay payWay; // 支付行为 }
在好比 每种支付方式 支付以前都须要校验一下支付金额是否是真确的,不能小于等于0 。由于校验方式,校验代码都是同样的,因此咱们能够定义一个 抽象类给抽象出来
public abstract class AbstractPayWay implements PayWay{ private Double money; private boolean verify(){ return money != null && money > 0; } /** * 这里实现了 PayWay 中的 pay 接口 方法 因此 AbstractPayWay 的子类 无需 实现该 方法, * 只须要 实现 doPay() 方法,而且 若是 doPay()方法被成功调用则说明确定 校验成功了。 */ @Override public boolean pay(){ boolean verify = this.verify(); if(!verify){ System.out.println("支付金额验证错误!"); return false; } return this.doPay(); } public abstract boolean doPay(); }
因此 WeixinPayWay ZhifubaoPayWay 支付的具体实现类能够改改成
/** * 继承 AbstractPayWay 因此无需 写公共的校验逻辑,直接写支付业务逻辑 * @author cyy */ public class WeixinPayWay extends AbstractPayWay{ @Override public boolean doPay() { System.out.println("这里无需校验支付金额,直接调用支付方法就行"); System.out.println("微信支付成功"); return false; } }
public class ZhifubaoPayWay extends AbstractPayWay{ @Override public boolean doPay() { System.out.println("这里无需校验支付金额,直接调用支付方法就行"); System.out.println("支付宝支付成功"); return false; } }
---------------------------------------------------------------------------------
问题:怎么在开发中尝试提炼抽象能力?
还得多 练习说设计模式和重构,固然在算法的基础上
在此以前坚持三个原则
第一次用到某个功能时,你写一个特定的解决方法;
第二次又用到的时候,你拷贝上一次的代码;
第三次出现的时候,你才着手"抽象化",写出通用的解决方法
转
从一个算命的段子谈谈抽象
这两天有幸去参加了张逸老师(《架构之美》(评注版)
以及《重构 : 改善既有代码的设计》(评注版)
的做者)的重构方面的培训(公司组织),写一篇文章分享一下这一两天听到的经典段子。
ps:这篇文章,可能没啥干货。由于我不可能去讲如何抽象,这种题材在公众号上看,你们看的太累了。并且你们去买任何一本重构方面的书,估计都讲的比我好。因此这篇文章,你们当段子看看便可。若是时间宝贵,慎重!能够关闭,偶不会怪你的!
段子
基本上全部的文章讲抽象举例的时候,都是举一些数学公式为例子。所以数学公式就是从大量数据中抽象出来的一个定律。那今天要分享的就是一个算命的段子。
话说,某日,三个书生去算命,他们问:"老先生,看看咱们今年乡试,谁可以高中?。"
老先生看了看他们的面相,只亮出了一根手指,就像下面这样
不久,三个书生乡试完毕,纷纷称赞,老先生算的准。
那么这是为何呢?聪明的各位读者想到了么?
由于
-
没有书生考上,这个一根手指表明一个书生都没考上
-
一个书生考上,这个一根手指表明只有一个书生考上
-
二个书生考上,这个一根手指表明只有一个书生没考上
-
三个书生考上,这个时候能够这么解释,正所谓,道生一,一辈子二,二生三,三生万物。这个一能够理解为圆满,三个都考上,不是很圆满么。
这时候,有人不服,问要是有四个书生考试怎么办?OK,也能够解释
-
没有书生考上,这个一根手指表明一个书生都没考上
-
一个书生考上,这个一根手指表明只有一个书生考上
-
二个书生考上,这个一根手指表明只有一对书生考上
-
三个书生考上,这个一根手指表明只有一个书生没考上
-
四个书生考上,这个一根手指表明圆满,你们都考上。
至于五个书生的状况,你们自行推导吧。
OK,讲完了。这个抽象就是从纷繁复杂的事物中提炼本质的过程。大家看,这个段子里的一就是对一切现象的抽象。那么一个程序员的抽象能力,就是他的软件设计能力。你们回忆一下本身作项目,从业务那边接需求,而后转化为代码,就是一个抽象的过程。你抽象的能力如何,就表明着你的代码质量如何。
若是你的抽象能力很LOW,那你的代码也很LOW,根本没法下降问题的复杂度。纵使你作了n个CRUD的项目,你也没法获得能力的提升。
何时抽象
首先,不要瞎抽象,不少人,没事瞎抽象,最后系统就抽筋了。并且,最开始系统需求不明确,需求变动频繁,瞎抽象,不利于后期维护。推荐仍是边写边抽象,一开始不要想太多。
具体原则,出自Martin Fowler在《Refactoring》
中提出的三次原则
第一次用到某个功能时,你写一个特定的解决方法;
第二次又用到的时候,你拷贝上一次的代码;
第三次出现的时候,你才着手"抽象化",写出通用的解决方法
理由:
(1)省事。若是一种功能只有一到两个地方会用到,就不须要在"抽象化"上面耗费时间了。
(2)容易发现模式. 说句实在话,我拿到一个需求,我也没法一下看出该用什么设计模式。都是写着写着,发现该用什么设计模式。因此边写边抽象化,比较符合实际状况。
(3)防止过分冗余。若是一种功能同时有多个实现,管理起来很是麻烦,修改的时候须要修改多处。所以须要进行抽象化。
如何抽象
这块就不细讲了,你们随便看几本重构方面的书,都讲的比我好。推荐《clean code》这本。
总的来讲,遵循罗伯特·马丁提出的五大原则 —— SOLID原则便可。
-
S Single Responsibility Principle - 单一职责原则
-
O Open Close Principle - 开闭原则
-
L Liskov Principle of Substitution - 里式替换原则
-
I Interface Segregation Principle - 接口隔离原则
-
D Dependency Inversion Principle - 依赖倒置原则
这几大原则,任何一本讲设计模式的书基本都有提到,就不赘述了。你们有兴趣能够自行查阅。
总结
频繁的需求变动会对软件的生命周期形成严重的杀伤力。若是你的抽象能力不足,你的代码就会愈加的臃肿,最后不得以进行二次开发。合理的抽象,能够下降软件代码的复杂度,加强软件的生命力,更是一个程序员编程能力和设计能力的体现。但愿你们在这方面下足功夫。