面向对象综述
封装
封装的意义,在于明确标识出允许外部使用的所有成员函数和数据项,或者叫接口。
有了封装,就可以明确区分内外,使得类实现者可以修改封装内的东西而不影响外部调用者;而外部调用者也可以知道自己不可以碰哪里。
这就提供一个良好的合作基础——或者说,只要接口这个基础约定不变,则代码改变不足为虑。
封装成员方法来间接操作类里面的成员变量
使用成员属性来间接访问类里面的成员变量
访问修饰符
private 私有的 只能在该类中访问 protected 受保护的 只能在该类和它的子类中访问 public 公有的 在任何地方都可以访问
继承和多态
什么是继承?
在OOP程序设计中,当我们定义一个class的时候,可以从某个现有的class继承,新的class称为子类(Subclass),而被继承的class称为基类、父类或超类(Base class、Super class)。
继承有什么好处?
继承可以把父类的所有功能都直接拿过来,这样就不必重零做起(比如object类实现了 ToString()方法,则继承自object的子类就无需再次实现该方法,直接调用)
子类只需要新增自己特有的方法,也可以把父类不适合的方法覆盖重写;
继承的第二个好处需要我们对代码做一点改进。当子类和父类都存在相同的Speak()
方法时,我们说,子类的Speak()
覆盖了父类的Speak()
,在代码运行的时候,总是会调用子类的Speak()
。这样,我们就获得了继承的另一个好处:多态。
有了继承,才能有多态。在调用类实例方法的时候,尽量把变量视作父类类型,这样,所有子类类型都可以正常被接收;
理解多态之前首先要对面向对象的里氏替换原则和开放封闭原则有所了解。
里氏替换原则(Liskov Substitution Principle):派生类(子类)对象能够替换其基类(超类)对象被使用。通俗一点的理解就是“子类是父类”,举个例子,
“男人是人,人不一定是男人”,当需要一个父类类型的对象的时候可以给一个子类类型的对象;当需要一个子类类型对象的时候给一个父类类型对象是不可以的!
开放封闭原则(Open Closed Principle):封装变化、降低耦合,软件实体应该是可扩展,而不可修改的。也就是说,对扩展是开放的,而对修改是封闭的。
因此,开放封闭原则主要体现在两个方面:对扩展开放,意味着有新的需求或变化时,可以对现有代码进行扩展,以适应新的情况。
对修改封闭,意味着类一旦设计完成,就可以独立完成其工作,而不要对类进行任何修改。
对这两个原则有一定了解之后就能更好的理解多态。
任何时候,如果没有合适的类可以继承,就继承自object类。
Person
package cn.cnki.test; public class Person { public void Speak() { System.out.println("人可以说话"); } }
Chinese
package cn.cnki.test; public class Chinese extends Person{ public void Speak() { System.out.println("中国人说中国话"); } }
Japan
package cn.cnki.test; public class Japan extends Person{ public void Speak() { System.out.println("日本人说日本话"); } }
Test
@Test public void FunctionTest() { Person p1=new Chinese(); p1.Speak(); Person p2=new Japan(); p2.Speak(); }
结果
一句话总结:多态就是父类类型的变量可以指向子类类型的对象,调用方法的时候调用的是子类的实现(父变量指向子对象,调用子实现)
为什么需要多态?多态的好处?如果没有多态会怎样?
继续上面的代码,如果此时我们新增一个子类American,只需新增一个American类继承自Person并重写Speak()方法即可,而父类是无需任何修改的。
至此,该程序的扩展性得到了提升,而又不需要查看源代码是如何实现的就可以扩展新功能。这就是多态带来的好处。
public class American extends Person{ public void Speak() { System.out.println("美国人说美国话"); } }
对于一个变量,我们只需要知道它是Person类型,无需确切地知道它的子类型,就可以放心地调用Speak()方法,而具体调用的Speak()方法是
作用在Chinese、Japan还是American对象上由运行时该对象的确切类型决定,这就是多态真正的威力:调用方只管调用,不管细节,
而当我们新增一种Person的子类时,只要确保Speak()方法编写正确,不用管原来的代码是如何调用的。
这就是著名的“开闭”原则:
对扩展开放:允许新增Person子类;
对修改封闭:不需要修改依赖Person类型的Speak()函数。
多态的本质作用就是把不同的子类对象都当作父类来看,可以屏蔽不同子类对象之间的差异,写出通用的代码,做出通用的编程,以适应需求的不断变化。
虚方法(virtual)
Java普通方法就是虚方法
抽象类和抽象方法
还是刚才的例子,我们发现Person这个父类,我们根本不需要使用它创建的对象,它存在的意义就是供子类来继承。所以我们可以用抽象类来优化它。
public abstract class Person { public abstract void Speak(); }
其他代码不变,输出结果依然相同。
由此可见,我们选择使用虚方法实现多态还是抽象类抽象方法实现多态,取决于我们是否需要使用基类实例化的对象.
Person类作为基类,Chinese,Japan类继承American,我们需要使用的是Chinese和Japan创建的对象,根本不需要使用Person创建的对象,所以在这里Person完全可以写成抽象类。
总而言之,是使用虚方法,或者抽象类抽象方法实现多态,视情况而定,什么情况?以上我说的两点~
接口
为什么需要接口?
我们知道少林寺的武僧会武术,歌剧院的演员会舞蹈,虽然所有人都可以Speak,但并非所有的人都会武术都会舞蹈吧,
此时如果我们在父类Person里面新增武术方法和舞蹈方法,然后让所有的子类都去实现这两个方法是否有所不妥呢?
你要是非要这样做,功能完全可以实现,可是这样违背了面向对象开放封闭原则。下次我要再扩展一个戏曲演员唱京剧方法,
我还要去源代码中看下是怎么实现的,然后在戏曲演员类中再次添加唱京剧方法,相同的功能,重复的代码,这样是不合理的,程序也不便于扩展;
接来下我们看接口是如何解决以上问题的
新增武术接口
public interface IWuShu { void WuShu();//武术 }
新增跳舞接口
public interface ITiaoWu { void Tiaowu();//跳舞 }
新增武僧类
public class WuSeng extends Person implements IWuShu { public void Speak() { System.out.println("武僧会念经"); } @Override public void WuShu() { System.out.println("武僧会武术"); } }
新增演员类
public class YanYuan extends Person implements ITiaoWu { public void Speak() { System.out.println("演员会唱歌"); } @Override public void Tiaowu() { System.out.println("演员会跳舞"); } }
可以看出实现了IWuShu接口的武僧可以会武术,实现了ITiaoWu接口的演员可以会跳舞。我们想要哪类人拥有哪项技能只需定义一个接口让其去实现即可。
实现了就具备该项技能,反之则不具备。
接口实现多态程序的扩展性得到了大大提升,以后不管是再扩展一个京剧演员,还是篮球运动员,创建一个类,实现这个接口,在主函数中添加该对象就可以了。
也不需要查看源代码是如何实现的,体现了开放封闭原则!
什么时候使用接口,什么时候使用抽象类
如果你想拥有一些方法,并且这些方法有默认实现,那么久使用抽象类
如果你想实现多继承,那么就是用接口吧,java不支持多继承,但是可以实现多个接口
小结
优先选用接口
在既要定义子类的行为,又要为子类提供公共的功能时,应选择抽象类。
虚方法(virtual)和抽象方法(abstract)
相同点:
虚方法(virtual)和抽象方法(abstract)都可以被派生类重写
不同点:
1.虚方法(virtual)有方法实体,抽象方法(abstract)没有方法实体【类似接口】
virtual void SayWord()
{
//代码
}
abstract void SayWord();
2.虚方法(virtual)在派生类中可以不重写,抽象方法(abstract)派生类中必须重写【类似接口】
abstract class Person
{
abstract void SayName();
}
class Man:Person
{
override void SayName()
{
//方法实体
}
}
3.抽象方法(abstract)必须声明在抽象类中,抽象方法结合抽象类实现多态