封装,接口,继承,覆盖,构造过程,多态,static、this、super、final用法
一、封装 (encapsulation)
定义:封装就是将客户端不应看到的信息包裹起来。使内部执行对外部来看不一种不透明的、是一个黑箱,客户端不需要内部资源就能达到他的目的。
1.事物的内部实现细节隐藏起来
2.对外提供一致的公共的接口――间接访问隐藏数据
3.可维护性
--------------------------------------------------------------------------------
二、 继承 (inherit)
JAVA继承特点
继承:父类的成员能否继承到子类?
对类成员访问的限制及子类继承情况: (从严 到宽 )
private 私有,本类内部 不能继承
(default) 本类+同包 同包子类可继承
protected 本类+同包+子类 可以继承
public 公开 任何地方都可以访问 能继承到子类
--------------------------------------------------------------------------------
覆盖
1、定义: 覆盖了一个方法并且对其重写,以求达到不同的作用。
2、用法:
a、最熟悉的覆盖就是对接口方法的实现
b、在继承中也可能会在子类覆盖父类中的方法
3、产生 “覆盖”的条件:
1、方法名:相同
2、参数表:相同(个数,类型)
3、访问限制符:相同或者更宽
4、返回值类型:相同 或者 子类返回的类型是父类返回的类型的子类
5、不能抛出比subclass(父类)更多的异常
注意:当我们在子类中创建的静态方法,它并不会覆盖父类中相同名字的静态方法。
class Parent
{
public void nonStaticMethod()
{
System.out.println("Parent's Non-Static Method is Called");
}
public static void staticMethod()
{
System.out.println("parent's static method is called");
}
}
class Child extends Parent
{
public void nonStaticMethod()
{
System.out.println("child's non-static method is called");
}
public static void staticMethod()
{
System.out.println("child's static method is called");
}
}
public class Test
{
public static void main(String args[])
{
Parent p1 = new Parent();
Parent p2 = new Child();
Child c = new Child();
System.out.print("Parent.static: "); Parent.staticMethod();
System.out.print("p1.static: "); p1.staticMethod();
System.out.print("p2.static: "); p2.staticMethod();
System.out.print("p1.nonStatic: "); p1.nonStaticMethod();
System.out.print("p2.nonStatic: "); p2.nonStaticMethod();
System.out.print("Child.static: "); Child.staticMethod();
System.out.print("c.static: "); c.staticMethod();
System.out.print("c.nonStatic: "); c.nonStaticMethod();
}
}
程序的运行结果为:
Parent.static: parent's static method is called
p1.static: parent's static method is called
p2.static: parent's static method is called
p1.nonStatic: Parent's Non-Static Method is Called
p2.nonStatic: child's non-static method is called
Child.static: child's static method is called
c.static: child's static method is called
c.nonStatic: child's non-static method is called
值得注意的是p2实际上是一个Child的类型的引用,然而在调用静态方法的时候,它执行的却是父类的静态方法,而不是Child的静态方法,而调用 p2的非静态方法的时候执行的是Child的非静态方法,为什么呢?原因是静态方法是在编译的时候把静态方法和类的引用类型进行匹配,而不是在运行的时候和类引用进行匹配。因此我们得出结论:当我们在子类中创建的静态方法,它并不会覆盖父类中相同名字的静态方法。
--------------------------------------------------------------------------------
构造
(java编程思想定义为:构造器初始化)
对象的构造过程:
首先为对象分配内存空间,包括其所有父类的可见或不可见的变量的空间,并初始化这些变量为默认值,如int类型为0,boolean类型为false,对象类型为null。。。。
然后用下述5个步骤来初始化这个新对象:
1)分配参数给指定的构造方法;
2)如果这个指定的构造方法的第一个语句是用this指针显式地调用本类的其它构造方法,则递归执行这5个 步骤;如果执行过程正常则跳到步骤5;
3)如果构造方法的第一个语句没有显式调用本类的其它构造方法,并且本类不是Object类(Object是所有其它类的祖先),则调用显式(用super指针)或隐式地指定的父类的构造方法,递归执行这5个步骤;如果执行过程正常则跳到步骤5;
4)按照变量在类内的定义顺序来初始化本类的变量,如果执行过程正常则跳到步骤5;
5)执行这个构造方法中余下的语句,如果执行过程正常则过程结束。
对分析本文的实例最重要的,用一句话说,就是“父类的构造方法调用发生在子类的变量初始化之前”。可以用下面的例子来证明:
列1
class Animal
{
Animal()
{
System.out.println("Animal");
}
}
class Cat extends Animal
{
Cat()
{
System.out.println("Cat");
}
}
class Store
{
Store()
{
System.out.println("Store");
}
}
public class Petstore extends Store
{
Cat cat = new Cat();
Petstore()
{
System.out.println("Petstore");
}
public static void main(String[] args)
{
new Petstore();
}
}
运行这段代码,它的执行结果如下:
Store
Animal
Cat
Petstore
从结果中可以看出,在创建一个Petstore类的实例时,首先调用了它的父类Store的构造方法;然后试图创建并初始化变量cat;在创建cat时,首先调用了Cat类的父类Animal的构造方法;其后才是Cat的构造方法主体,最后才是Petstore类的构造方法的主体。
列2
class Animal
{
Animal()
{
System.out.println("Animal");
}
}
class Cat extends Animal
{
Cat()
{
System.out.println("Cat");
}
}
class Cat2 extends Animal
{
Cat2()
{
System.out.println("Cat2");
}
}
class Store
{
Cat2 cat=new Cat2();
Store()
{
System.out.println("Store");
}
}
public class Petstore extends Store
{
Cat cat = new Cat();
Petstore()
{
System.out.println("Petstore");
}
public static void main(String[] args)
{
new Petstore();
}
}
运行这段代码,它的执行结果如下:
Animal
Cat2
Store
Animal
Cat
Petstore
这例1和例2,使我更了解了类的构造过程:
类在new实例的时候
a。第一步构造类的成员变量
这里需要注意的:
a)即使变量定义散布于方法定义之间,它们仍旧会在任何方法(包括构造函数)被调用之前得到初始化
b)初始化的顺序是先“静态”对象(初始化过后,静态对象不会再被初始化),而后是“非静态”对象。
第二步调用构造方法。
b。如果有父类,那么就是先构造父类的成员变量,然后再调用父类的构造方法,然后再a。
--------------------------------------------------------------------------------
this用法
java中的this随处可见,用法也多,现在整理有几点:
1. this是指当前对象自己。
当在一个类中要明确指出使用对象自己的的变量或函数时就应该加上this引用。如下面这个例子中:
public class Hello
{
String s = "Hello";
public Hello(String s)
{
System.out.println("s = " + s);
System.out.println("1 -> this.s = " + this.s);
this.s = s;
System.out.println("2 -> this.s = " + this.s);
}
public static void main(String[] args)
{
Hello x=new Hello("HelloWorld!");
}
}
运行结果:
s = HelloWorld!
1 -> this.s = Hello
2 -> this.s = HelloWorld!
在这个例子中,构造函数Hello中,参数s与类Hello的变量s同名,这时如果直接对s进行操作则是对参数s进行操作。
若要对类Hello的成员变量s进行操作就应该用this进行引用
class A
{
public A()
{
new B(this).print();
}
public void print()
{
System.out.println("Hello from A!");
}
}
class B
{
A a;
public B(A a)
{
this.a = a;
}
public void print()
{
a.print();
System.out.println("Hello from B!");
}
}
public class C
{
public static void main(String[] args)
{
A x=new A();
}
}
运行结果:
Hello from A!
Hello from B!
在这个例子中,对象A的构造函数中,用new B(this)把对象A自己作为参数传递给了对象B的构造函数。
2。在构造函数中,通过this可以调用同一class中别的构造函数,如
class Flower
{
Flower (int pig)
{
System.out.println("pig:"+ pig );
}
Flower(String ss)
{
ss="string";
System.out.println("ss:"+ ss);
}
Flower(int pig, String ss)
{
this(pig);
System.out.println("pigpig"+ pig);
}
}
public class Zx
{
public static void main(String[] args)
{
Flower a=new Flower(7,"java");
}
}
运行结果:
pig:7;
pigpig:7;
值得注意的是:
1:在构造调用另一个构造函数,调用动作必须置于最起始的位置。
2:不能在构造函数以外的任何函数内调用构造函数。
--------------------------------------------------------------------------------
static
1. 调用静态方法时,不需要产生对象.因为静态方法属于类本身,而不属于对象. 调用时: 类名.静态方法名() 就可以
2.
class Point
{
int x;
static int y; //定义了1个静态变量
static void output()
{
System.out.println("out");
}
public static void main(String[] args)
{
Point.output(); //调用静态方法
Point pt1=new point();
Point pt2=new point();
pt1.y=5;
pt2.y=6;
System.out.println(pt1.y);
System.out.println(pt2.y);
}
}
结果
pt1 6
pt2 6
静态常量和静态方法,都只属于某个类,加载的时候 已经有内存空间了.所有对象都共享同1个静态变量,同1个静态方法..引用直接通过类名引用.
但非静态方法,可以调用静态方法和静态变量;但反过来,静态方法不能调用非静态变量和非静态方法.(即 x)
--------------------------------------------------------------------------------
final
final类不能被继承,因此final类的成员方法没有机会被覆盖,默认都是final的。在设计类时候,如果这个类不需要有子类,类的实现细节不允许改变,并且确信这个类不会载被扩展,那么就设计为final类。
1.定义及编程技巧
final类不能被继承,没有子类,final类中的方法默认是final的。
final方法不能被子类的方法覆盖,但可以被继承。
final成员变量表示常量,只能被赋值一次,赋值后值不再改变。
final不能用于修饰构造方法。
注意:父类的private成员方法是不能被子类方法覆盖的,因此private类型的方法默认是final类型的
2、final方法
如果一个类不允许其子类覆盖某个方法,则可以把这个方法声明为final方法。
使用final方法的原因有二:
第一、把方法锁定,防止任何继承类修改它的意义和实现。
第二、高效。编译器在遇到调用final方法时候会转入内嵌机制,大大提高执行效率。
技巧:final static 类型成员变量。static使得变量只存在一个拷贝。final使得它不能改变。
3.关于final成员赋值的讨论:
a.final 成员能且只能被初始化一次
b.final成员必须在声明时或者在构造方法中被初始化,而不能在其它的地方被初始化。
eg1
public class Aox
{
int height;
Aox(int h)
{
height=h;
}
void bb()
{
System.out.println(height);
}
public static void main(String[] args)
{
Aox boxobj = new Aox(25);
boxobj.bb();
boxobj=new Aox(32);
boxobj.bb();
}
}
运行结果:
25
32
eg2
public class Aox
{
int height;
Aox(int h)
{
height=h;
}
void bb()
{
System.out.println(height);
}
public static void main(String[] args)
{
Aox boxobj = new Aox(25);
boxobj.bb();
// boxobj=new Aox(32); 出错!
boxobj.bb();
}
}
--------------------------------------------------------------------------------
super
1.super(参数):调用基类中的某一个构造函数(应该为构造函数中的第一条语句)
2.super: 它引用当前对象的直接父类中的成员(用来访问直接父类中被隐藏的父类中成员数据或函数,基类与派生类中有相同成员定义时)
如:super.变量名
super.成员函数据名(实参)
--------------------------------------------------------------------------------
包
1.从本质上来说,包是将类组合在一起形成代码模块的一种机制,可以将包看成一个“文件夹”,而包里的各类,则是“文件”。
用途:避免命名冲突;可以在更广的范围保护类,数据和方法。
2.创建包
package a; //a是包名
3.导入包
1) import package a.* //a是包名。字符"*"则导入了包里的所有类和接口。
2) import package a.b //a是包名,b是类名。该操作导入了a包里的b类,而不会导入其它类。
4.使用其它包的类,而没有用import导入该包,操作如下:
a.b x= new a.b(); //a是包名,b是a包里的类,x是刚创建的该类的实例名。
怎样理解封装,继承,多态!三者的区别?
封装:每个对象都包括进行操作所需要的所有信息,而不依赖于其他对象来完成自己的操作。通过类的实例来实现。
好处:良好的封装可以降低耦合度;类的内部可以自由修改;类具有对外的清晰接口。
继承:IS-A的关系。A is-a B:A是B,A可以继承B。A是B的一个特例,特殊化,A又可以具备自己独有的个性。三点:
子类拥有父类非private的属性和功能(父类的构造函数例外,可以用base关键字访问,base代表着父类);
子类具有自己的属性和功能,即子类可以扩展父类没有的属性和功能;
子类可以以自己的方式重写父类的功能。
缺点:父类变,子类不得不变,父子是一种强耦合的关系。
多态:不同的对象可以执行相同的动作,但要通过他们自己的实现代码来执行。注意点:
子类以父类的身份出现;
子类在运行时以自己的方式实现;
子类以父类的身份出现时,子类特有的属性和方法不可以使用。
为了使子类的实例完全接替来自父类的类成员,父类必须将该成员声明为虚拟的(virtual),子类可以选择使用override将父类的实现替换为自己的实现,这就是多态。
1。类是对对象的一个抽象,同时类也为对象进行了封装。所谓封装是说类的设计者只是为使用者提供类 对象可以访问的部分,而对于类中其他隐藏起来的成员变量 方法,用户不能访问。
实现方式:A:在类的定义中设置对对象中成员变量和方法进行访问的权限;
B:提供一个统一的供其他类引用的方法;
C:其它对象不能直接修改文本对象所拥有的属性和方法。
2。访问权限:
A:private的成员变量和方法只能被这个类本身的方法访问;
B:默认的成员变量和方法只被同一个包内的其它所有类都可访问,包外不可;
C: protected的成员可以被这个类的本身、它的子类(同不同包均可)访问;
D: public 完全公开。一个程序里最多只能有一个类被修饰为public,若程序中没有任何public 类,且文件名是程序中的一个类名,则该类被视做public
注:不能用protected和private饰!!!!
3。类的继承:
3.1 类继承的实现:
A:java中Object类是java中所有类的祖宗!(P209)
儿子A extends 老爸------只能有一个老爸!
3.2 this和super:
this: this.数据成员 this.实例方法 this(参数)--引用本类中其他构造方法。
super:表示当前对象父类对象的引用
super.数据成员 super.实例方法 super(参数)--引用父类的构造方法。--通常在实现子 类的构造方法时,要先调用父类的构造方法!并且用来调用父类构造方法的super()必须放在子类构造方法的句首
3.3 初始化顺序:
A 对象的初始化顺序: 先静态成员,再非静态成员,再构造方法,
B 继承中构造方法的调用顺序: 先父类,再子类。
3.4 对象之间的类型转换:
对象转换的圣经:只能说猫是哺乳动物,不能说哺乳动物是猫!!!当说猫是哺乳动物时,猫将失掉猫独有的属性和行为!!!(作为猫时新增的成员变量和方法)
哺乳动物 aa=new 猫()---猫是哺乳动物(要通过IS-A测试)
4.千呼万唤始出来------多态!
所谓多态就是同一方法获得不同的行为特性!
分为:
A 编译时多态性---重载 (参数个数不同或参数类型不同)
B 动态多态性----覆盖( 改写后的方法不能比覆盖的方法有更严格的访问权限--只能更加开放& 改写后的方法不能比被覆盖的法产生更多的例外)
******************************************************************
放点小音乐吧!
**** ******* 第一曲 final:
1。final type 变量名=初值---常量
2。final 方法---该方法任何时候都不可以覆盖,但可以被重载。
3。final 类 ----该类不能被继承并覆盖其内容。(像System类,String类)
第二曲 native: 本地引用说明符^@^
java允许在程序中直接引用本地机上非java语言编写的程序。
native type programName([参数列表]);
programName是要引用的程序名,以方法的形式出现。由于是引用,具体执行的操作都包含在源程序中,所有programName后无具体方法体!
******************************************************************
一。深入继承:
1》设计继承树:
(1)找出具有共同属性和行为的对象(狼和狗与羊^^---动物共同的属性)
(2)设计代表共同状态与行为的类(都属于动物!---父类)
(3)决定子类是否需要让某项行为有特定不同运作的方式(都会吃,但羊只吃草---方法的覆盖)
(4)通过寻找使用共同行为的子类找出更多抽象化的机会(狼和狗属于犬科!!!--再继承)
(5)检查设计是否合理(IS-A)
(6)大功告成!
2》继承的意义:
(1)避免了重复的代码(子类会时时响应父类的改变)
(2)定义出共同的协议(服务于多态^^)
?当定主义出一组类的父型时,你可以用子型的任何类来填补任何需要或期待父型的位置?
二。深入多态:
(1) class Vet{
public void giveShot(Animal a){
a.makeNoise();
}
}
class PetOwner{
public void start(){
Vet v=new Vet();
Dog d=new Dog();
Hippo h=new Hippo();
v.giveShot(d);
V.giveSHot(h);
}
}
在此例中如果用Animal类型来声明它的参数(返回类型、数组的类型),则程序代码就可以处理所有Animal的子类。这意味 着其它人只要注意到要扩充过Animal就可以利用你的Vet!(P168)
如果此例不用多态,那么每一种动物都要写出不同的方法--这是何等悲哀!!
(2)接口:
多态意味着“很多形式”,可以把儿子当作爸爸或爷爷!!
可以通过继承来定义相关类间的共同协议(方法就是协议的标志!),这样就等同于在对其他程序代码声明:“我所有的子类都能肠系用这些方法来执行这几项工作!”
为了避免出现“致命方块”所以java不允许出现多继承,接口正是我们的救星!java接口是100% 的纯抽象类。而且使用接口可以继承一个以上的来源,同时其他类也可以实现同一个接口中。所以可以为不同的需求组合出不同的继承层次!
三。如何判断应该是设计类、子类、抽象类或接口呢?
(1)如果新类无法对其他的类通过IS-A测试时,就设计不继承其他类的类。
(2)只有在需要某类的特殊化版本时,以覆盖或增加新的方法来继承现有的类。
(3)当你需要定义一群子类的模板,又不想让其他人初始化此模版时,设计出抽象的类给他们用。
(4)如果想要只定义出类可以扮演的角色,使用接口。
说那么多
自己 经常用用就知道了~封装,继承,多态
封装 就是 把 孩子放到一块管理
继承 就是 生孩子
多态 男人 ,女人 ,人妖 ,就是 人的多态
你认为大家不理解吗?这些东西,只有用了先知道。
多态,你写过名称相同,参数不同的函数吗?
继承,你的所有aspx页面,不都是继承Page吗?
封装,你没有自己写过类吗?你类里没有封装方法吗?
接口主要涉及跨平台、跨语言、移植性要求较高的编程
一般情况下不会用到的......
面向对象的三个特征.继承,封装和多态.
C#是一种现代的面向对象的语言.
继承(inheritance):继承是一个面向对象的词语.说明,一个类(派生类)能分享,其它类(基类)的特征和行为.派
生类和基类是"is a"的关系.
base classes(基类):通常基类可以自己实例化,或被继承.派生类继承基类中的成员,被标记为protected或更大
的权限.语法: class (derive class name):(base class name)
例子:
//基类
public class Contact
{
//默认私有的字段
string name;
string email;
string address;
//构造函数
public Contact()
{
// statements ...
}
//属性
public string Name
{
get
{
return name;
}
set
{
name = value;
}
}
public string Email
{
get
{
return email;
}
set
{
email = value;
}
}
public string Address
{
get
{
return address;
}
set
{
address = value;
}
}
}
//派生类
public class Customer : Contact
{
//自己的私有字段
string gender;
decimal income;
public Customer()
{
// statements ...
}
}
在上面的例子中,Customer 是
Contact的子类,不但,继承了父类的成员,name,email,address;还有自己的成员,gender,income.
abstract classes(抽象类):抽象类是一种特殊的基类.除过普通的类成员,它们还有抽象的类成员.抽象类成员,
是不能被实例化的方法和属性.所有直接从抽象类派生的类,必须实现抽象的方法和属性.抽象类不能被实例化.
例子:
//抽象类
abstract public class Contact
{
protected string name;
public Contact()
{
// statements...
}
//抽象方法
public abstract void generateReport();
//抽象属性
abstract public string Name
{
get;
set;
}}
public class Customer : Contact
{
string gender;
decimal income;
int numberOfVisits;
public Customer()
{
// statements
}
public override void generateReport()
{
// unique report
}
public override string Name
{
get
{
numberOfVisits++;
return name;
}
set
{
name = value;
numberOfVisits = 0;
}
}
}
public class SiteOwner : Contact
{
int siteHits;
string mySite;
public SiteOwner()
{
// statements...
}
public override void generateReport()
{
// unique report
}
public override string Name
{
get
{
siteHits++;
return name;
}
set
{
name = value;
siteHits = 0;
}
}
}
上面的例子,定义了三个类.一个抽象类,两个派生类.实现了父类的方法和属性."override"修饰符,实现了抽象
类的方法.
Calling Base Class Members(调用基类成员)
派生类能调用基类成员,如果,成员的修饰符是"protected"或更大权限.在适当的上下文条件下,好像调用自己的
成员一样.
例子:
abstract public class Contact
{
private string address;
private string city;
private string state;
private string zip;
public string FullAddress()
{
string fullAddress =
address + '/n' +
city + ',' + state + ' ' + zip;
return fullAddress;
}
}
public class Customer : Contact
{
public string GenerateReport()
{
string fullAddress = FullAddress();
// do some other stuff...
return fullAddress;
}
}
上面的例子中,派生类调用基类的方法:FullAddress();
基类的构造函数,可以被派生类调用,用base().
例子:
abstract public class Contact
{
private string address;
public Contact(string address)
{
this.address = address;
}
}
public class Customer : Contact
{
public Customer(string address) : base(address)
{
}
}
例子中,派生类没有address成员,可以调用基类的构造函数.
Hiding Base Class Members(隐藏基类成员)
派生类可以和基类有同样名字的成员.这时,就会隐藏基类的成员.
例子:
abstract public class Contact
{
private string address;
private string city;
private string state;
private string zip;
public string FullAddress()
{
string fullAddress =
address + '/n' +
city + ',' + state + ' ' + zip;
return fullAddress;
}
}
public class SiteOwner : Contact
{
public string FullAddress()
{
string fullAddress;
// create an address...
return fullAddress;
}
}
在例子中,派生类和基类有同样的成员,FullAddress(),当调用时,基类的方法会被隐藏.
尽管基类的成员被隐藏,仍然可以访问基类的成员,通过,base关键字,调用基类的引用.
例子:
abstract public class Contact
{
private string address;
private string city;
private string state;
private string zip;
public string FullAddress()
{
string fullAddress =
address + '/n' +
city + ',' + state + ' ' + zip;
return fullAddress;
}
}
public class SiteOwner : Contact
{
public string FullAddress()
{
string fullAddress = base.FullAddress();
// do some other stuff...
return fullAddress;
}
}
在例子中,派生类调用基类的成员,用base引用.
visioning(版本)
例子:
using System;
public class WebSite
{
public string SiteName;
public string URL;
public string Description;
public WebSite()
{
}
public WebSite( string strSiteName, string strURL, string strDescription )
{
SiteName = strSiteName;
URL = strURL;
Description = strDescription;
}
public override string ToString()
{
return SiteName + ", " +
URL + ", " +
Description;
}
}
public class Contact
{
public string address;
public string city;
public string state;
public string zip;
public string FullAddress()
{
string fullAddress =
address + '/n' +
city + ',' + state + ' ' + zip;
return fullAddress;
}
}
public class SiteOwner : Contact
{
int siteHits;
string name;
WebSite mySite;
public SiteOwner()
{
mySite = new WebSite();
siteHits = 0;
}
public SiteOwner(string aName, WebSite aSite)
{
mySite = new WebSite(aSite.SiteName,
aSite.URL,
aSite.Description);
Name = aName;
}
new public string FullAddress()
{
string fullAddress = mySite.ToString();
return fullAddress;
}
public string Name
{
get
{
siteHits++;
return name;
}
set
{
name = value;
siteHits = 0;
}
}
}
public class Test
{
public static void Main()
{
WebSite mySite = new WebSite("Le Financier",
"http://www.LeFinancier.com",
"Fancy Financial Site");
SiteOwner anOwner = new SiteOwner("John Doe", mySite);
string address;
anOwner.address = "123 Lane Lane";
anOwner.city = "Some Town";
anOwner.state = "HI";
anOwner.zip = "45678";
address = anOwner.FullAddress(); // Different Results
Console.WriteLine("Address: /n{0}/n", address);
}
}
例子中,派生类用new修饰符,说明,和基类有同样名字的成员.
sealed classed(密封类)
密封类是不能被继承的类.为了避免从一个类中继承,就要生成密封类.
例子:
//密封类
public sealed class CustomerStats
{
string gender;
decimal income;
int numberOfVisits;
public CustomerStats()
{
}
}
public class CustomerInfo : CustomerStats // error
{
}
public class Customer
{
CustomerStats myStats; // okay
}
例子中,密封类不能被继承.
你用不到接口,说了也没有用。
petshop就用到了接口,数据工厂,具体不不是很理解。
接口只定义方法,不实现。数据工厂的原理似乎是根据接口调用。
你就可以不考虑接口是怎么实现的。
比如你dal定义的统一的接口,这个接口可以用操作access的类实现。
也可以用操作ms sql的类实现。你改变了操作类,但是你调用的时候是用接口。
所以不用改代码,这一块代码。
封装:比如你只需要指定他是一个人,能干什么就行,而不需要知道他是怎么去干的。你只能通过命令让他去干事情,而不是你拉着他的手一步一步教他去怎么做。
继承:比如你老爸是个人,你是你老爸的儿子,那么你也是一个人,你老爸有的东西你都有,但是你有不同于你老爸,你除了拥有你老爸的东西外还多出了自己东西。
多态:比如一个人,他拥有手,但是如果你让他举起一只手,他可能举起左手,也可能举起右手,但是如果你让他举起左手,那他就能唯一确定的举起左手。
这其实是典型的滥用继承的一个现象。如今所有结构化编程人员都用OOPL了,所以OOPL编程要谨慎对待那种滥用OO的情况,用过了比不会用更危险,因为它发现和矫正起来更难。
OO设计中,如果B类继承自A类,就是说B类对象“is_a”A类,例如猪是动物,房子是建筑物,白马是马。同一个对象有不同的身份(接口),因此我们可以有两种不同的分类,但是并不改变“同一个对象”的基本概念,xyz住院病人是xyz病人,是同一个病人,而不是两个病人。如果你说儿子继承老爸,就等于说儿子和老爸是同一个人,这就诡异和胡乱使用了继承概念。生活中的遗产继承如果你“按名词”对号入座为OO设计中的继承,就会出现设计上的严重错误。所以最怕有些人不问名词的内涵,仅凭名词相似就附会概念的情况。
不要以为只有不懂OO的人会犯这个错误,我看过并且我在csdn上推荐过一本关于.net架构设计的入门书,中国人写的,虽然它的具体内容显得清晰易懂很好,但是我也说过那本书上在前头讲继承和组合的架构概念是竟然用2、3页篇幅以“富爸爸、穷爸爸”留遗产的例子来比喻继承技术,其实是很荒唐的。虽然那本书我推荐作为架构入门书看看,因为其后边的实际具体内容还是很好的,但是前边一章理论方面的基本概念的问题也确实有误导性。
编写代码的语法是不应该滥用的,你要首先想到用代码来表达你的设计思想,而不是用代码来败坏你本来清晰的设计思想。所以,设计思想,可以给完全不懂编程的人使用,才是真正的设计思想。如果你为了编程方便,根据“名词匹配”哪种方式去附会地学习,早晚会像邯郸学步一样可痛苦地不得不设计转型。研究软件设计,请忘记你的编程语言,忘记的计算机技术,认真研究语言、逻辑,在一张白纸上你可以画出设计模型,并且可以将给所有编程的外行听你的设计模型。
封装:每个对象都包括进行操作所需要的所有信息,而不依赖于其他对象来完成自己的操作。通过类的实例来实现。
好处:良好的封装可以降低耦合度;类的内部可以自由修改;类具有对外的清晰接口。
继承:IS-A的关系。A is-a B:A是B,A可以继承B。A是B的一个特例,特殊化,A又可以具备自己独有的个性。三点:
子类拥有父类非private的属性和功能(父类的构造函数例外,可以用base关键字访问,base代表着父类);
子类具有自己的属性和功能,即子类可以扩展父类没有的属性和功能;
子类可以以自己的方式重写父类的功能。
缺点:父类变,子类不得不变,父子是一种强耦合的关系。
多态:不同的对象可以执行相同的动作,但要通过他们自己的实现代码来执行。注意点:
子类以父类的身份出现;
子类在运行时以自己的方式实现;
子类以父类的身份出现时,子类特有的属性和方法不可以使用。
为了使子类的实例完全接替来自父类的类成员,父类必须将该成员声明为虚拟的(virtual),子类可以选择使用override将父类的实现替换为自己的实现,这就是多态。
这其实也是一种很过时的说法。随着进15年OO的流行,过去大书特书“优先使用基于对象的组合,而不是类的继承来实现复用”那些人都掉头来写如何用继承来实现复用了。例如微软在推广COM+体系的时代,几乎所有专为微软推广编程开发语言的编程书都这么说。但是它自己发布了.net战略,大家可以看看.net framework中的实现,例如asp.net控件或者winForm控件体系,不正是使用到了继承了吗?从一个根本不直接支持继承的COM体系过度到支持单继承的.net体系,写微软的编程书的作者于是就改口了。道理很简单,这句话没有一个可操作的原则性,什么叫做“优先”,当被自己证明是可以改变的时候,他就失去了可信性。
所以我说,“但是他们大多也没有从OO设计而纯粹是从编程角度去写的”、“编写代码的语法是不应该滥用的,你要首先想到用代码来表达你的设计思想”。
我们应该首先掌握OOAD设计思想,而不是纠缠于某一种编程语言,更不是纠缠于编程时是否方便。如果你因为编程时发现某些方法的名称恰好类似,甚至代码恰好类似,为了编程方便你把不同的类制造了一个继承关系,这跟设计能否保持一致就值得深深地怀疑和检验。我们设计软件,从分析、设计、编码、测试的逻辑和风格要保持一致,不能分析设计时很明白,一写代码就变了。“你要首先想到用代码来表达你的设计思想,而不是用代码来败坏你本来清晰的设计思想”。如果觉得这比较玄,那么你就忘记编程语言,假设自己是一个完全不懂编程的人首先来进行逻辑设计,然后你一定要设置检测程序来从系统外部发现实现时那种因为滥用继承、多态等来败坏程序设计逻辑一致性的现象,这样你的产品才能用的更长、更新改造过程来的更顺利。
封装:package 就等于打个包,包里面的东西是秘密,但是这个包有特定的用途,但是只能用名称来调用使用.
继承:inherit 就是copy,儿子copy了他老爸的基因,所以儿子有他老爸的所有的东西,包括老爸的钱就是自己的钱
(但老爸的老婆,可不是自己的老婆了,老婆就叫做构造函数)是不能继续的);
多态:multiState 比如说你去澡堂里洗澡,在池溏里,你一不小心往里面撒了泡尿,其它人做出什么样的反应呢?
有人打你,有的人骂你,有的人用眼睛瞪你,有的人... 根据你做出的这一件事情,而其它对象有不同的反应,这就是多态.
我并不赞同这种说法,软件设计的思想从初始的瀑布模型到增量迭代,再到现在热门敏捷开发,其实是一步一个脚印走过来的,或许一些概念已经变更,但某些思想仍然在继续传承。就像现在 C#、Java满天飞,但是对于基本的数据结构和算法,像语言表面那么热闹吗?你用 C# 实现一个冒泡排序,和用 C 甚至是 Javascript 实现在本质上有很大的区别吗?抛开这些繁华的表面,我们是否有引以为根的东西,我觉得这才是重要的。
我入行的时间不是太长,做到现在快满一年了,外面漂泊了许久,因为种种原因,最后操起了程序员这个职业,抽以在语言特性方面的理解上有些浅薄(呵,除对 .net 框架算是稍有了解之外,对于 COM 什么的纯粹是两眼一抹黑)。但是,我相信,即使在程序设计这个日新月异的行业内,仍然有些东西在很长时间内是不会改变的,而这些是程序设计所共通的,每种语言都不可能绕过的东西。
所以,我不认为一个很重要并且目前仍然很有用的原则会因为时间的流逝而打上过时的标签,从商业的手段来推导技术上的原则,是否有些不恰当呢?况且微软不是全部,更何况微软的控件体系中对对象组合的应用,远多于继承的方式,可以查阅一下 MSDN,几乎每个控件类的下面都有一长串所实现接口的列表,这些接口是用来做什么的呢?
其实我一直有个想法,就是对于需求的变更,尽量做到不更改原有的代码,而是通过增加新的代码,以一种插件的形式添加到原有系统中,这要怎么才能做到呢?必须设计良好的接口,那么就可以动态的用具有新功能的对象替换掉原有的功能模块对象,从而实现系统功能的变更,这其实也是基于一个典型的对象组合的应用。
类与接口在 OO 设计中是不同的概念,这在很多年前就已经是定论了,虽然它们具有类似的表述形式,在一些语言中,还是用抽象类的概念去描述接口。但是,我认为,类侧重的是代码,描述的是它如何实现这个功能,而接口,则侧重于表述一个对象应该实现什么样的功能,而并不关心如何实现,这正是对象能够组合的基础。如果都仅只是类型,.NET 还有 Java 为何要把接口单独定义出来,说接口是类型,那仅只是从编程的角度来理解,而不是 OO 的角度。