A.2 类与实例
对象是一个自包含的实体,用一组可识别的特性和行为来标识。面向对象编程(OOP),其实就是针对对象来进行编程的意思。
类就是具有相同的属性和功能的对象的抽象的集合。
class 是表示定义类的关键字,要注意,第一,类名称首字母记着要大字。多个单词则各个首字母大写;第二,对外公开的方法需要用public修饰符。
A.3 构造方法
构造方法又叫构造函数,其实就是对类进行初始化。构造方法与类同名,无返回值,也不需要void,在new时候调用。也就是说,在类创建时,就是调用构造方法的时候了。
所有类都有构造方法,如果你不编码则系统默认生成空的构造方法,若你有定义的构造方法,那么默认的构造方法就会失效了。
例:
class Cat
{ private string name = ""; //声明Cat类的私有字符串变量name
public Cat(string name) //定义Cat类的构造方法,参数是输入一个字符串
{
this.name = name; //将参数赋值给私有变量name
}
public string Shout()
{
return "我的名字叫" + name + " 喵";
}
}
A.4 方法重载
方法重载提供了创建同名的多个方法的能力,但这些方法需使用不同的参数类型。注意并不是只有构造方法可能重载,普通方法也可以重载的。
注意方法重载时,两个方法必须要方法名相同,但参数类型或个数必须要有所不同,否则重载就没有意义了。它的好处在于可在不改变原方法的基础上,新增功能。
例:
class Cat
{ private string name = "";
public Cat(string name)
{
this.name = name;
}
public Cat() //将构造方法重载
{
this.name = "无名";
}
public string Shout()
{
return "我的名字叫" + name + " 喵";
}
}
A.5 属性与修饰符
属性是一个方法或一对方法,但在调用它的代码看来,它是一个字段,即属性适合于以字段的方法使用方法调用的场合。
(属性结合了字段和方法的多个方面,对于对象的用户,属性显示为字段,访问该属性使用和字段完全相同的语法。对于类的实现者,属性是一个或两个代码块,表现为一个get访问器或一个set访问器。当读取属性时,执行get访问器的代码块;当向属性分配一个新值时,执行set访问器的代码块。不具有set访问器的属性被视为只读属性,不具有get访问器的属性被视为只写属性,具时具有这两个访问器的属性是读写属性)
字段是存储类要满足其设计所需要的数据,字段是与类相关的变量。
变量私有的叫字段,公有的是属性;对于方法而言,同样也就私有方法和公有方法了,一般无需要对外界公开的方法都应该设置其修修饰符为private(私有)。这才有利于“封装”。
A.6 封装
每个对象都包含它能进行操作所需要的所有信息,这个特性称为封装,因此对象不必依赖其它对象来完成自己的操作。这样方法和属性包装在类中,通过类的实例来实现。
封装的好处:
第一、良好的封装能够减少耦合;
第二、类内部的实现可以自由地修改;
第三、类具有清晰的对外接口。
A.7 继承
对象的继承代表了一种“is_a”的关系,如果两个对象A和B,可以描述为’B是A’,则表明B可以继承A。如“猫是哺乳动物”,就说明了猫与哺乳动物之间继承与被继承的关系。实际上,继承者还可以理解为是对被继承者的特殊化,因为它除了具备被继承者的特殊性外,还具备自己独有的个性。
在继承关系中,继承者可以完全替换被继承者,反之则不成立。所以,我们在描述继承的“is_a”关系时,是不能相互颠倒的。
继承定义了类如何相互关联,共享特性。继承的工作方式是,定义父类和子类,或叫做基类和派生类,其中子类继承父类的所有特性。子类不但继承了父类的所有特性,还可以定义新的特性。
学习继承记住三句话,如果子类继承于父类,
第一、子类拥有父类非private的属性和功能;(即:只有被public、protected、internal访问修饰符修饰的成员才可以被继承,但不包括构造方法和析构方法)
(注:protected表示继承时子类可以对基类有完全访问权。)
第二、子类具有自己的属性和功能,即子类可以扩展父类没有的属性和功能;
第三、子类还可以以自己的方式实现父类的功能(方法重写)。
在C#中,子类从它的父类中继承的成员有方法、域、属性、索引指示器、但对于构造方法,有一些特殊,它不能被继承,只能被调用。对于调用父类的成员,可以用base关键字。
不用继承的话,如果要修改功能,就必须在所有重复的方法中修改,代码越多,出错的可能性就越大,而继承的优点是,继承使得所有子类公共的部分都放在了父类,使得代码得到了共享,这就避免了重复,另外,继承可以使得修改或扩展继承而来的实现都较为容易。
继承是有缺点的,那就是父类变,则子类不得不变。让狗去继承于猫,显然不是什么好的设计,另外,继承会破坏包装,你类实现细节暴露给子类,这其实是增大了两个类之间的耦合性。
当两个类之间具备“is-a”的关系时,就可以考虑用继承了,因为这表示一个类是另一个类的特殊种类,而当两个类之间是“has-a”的关系时,表示某个角色具有某一项责任,此时不合适用继承。
A.8 多态
面向对象的第三大特性---多态。
多态表示不同的对象可以执行相同的动作,但要通过它们自己的实现代码来执行。
注意几点:
第一,子类以父类的身份出现;
第二,子类在工作时以自己的方式来实现;
第三,子类以父类的身份出现时,子类特有的属性和方法不可以使用;
虚方法和方法重写:
为了使子类的实例完全接替来自父类的类成员,父类必须将该成员声明为虚拟的。这是通过在该成员的返回类型之前添加virtual关键字来实现。
通常虚拟的是方法,但其实除了字段不能是虚拟的,属性、事件和索引器都可以是虚拟的。尽管方法可以是虚拟的,但虚方法还是有方法体,可以实现做些事情。然后,子类可以选择使用override关键字,将父类实现替换为它自己的实现,这就是方法重写Override,或者叫做方法覆写。
A.9 重构
A.10 抽象类
C#允许把类和方法声明为abstract,即抽象类和抽象方法。
加abstract关键字,表明是抽象类 |
在方法返回值前加abstract表明此方法是抽象方法,直接在括号后加“ ;” |
{
……
Protected abstract string GetShoutSound();
}
抽象类需要注意几点,
第一,抽象类不能实例化; 如,new Animal(),即实例化一个动物,动物是一个抽象的名词,没有具体对象与之对应。
第二,抽象方法是必须被子类重写的方法,不重写的话,它的存在又有什么意义呢?其实抽象方法可以被看成是没有实现体的虚方法;
第三,如果类中包含抽象方法,那么类就必须定义为抽象类,不论是否还包含其它一般方法。
我们应该考虑让抽象类拥有尽可能多的共同代码,拥有尽可能少的数据。
抽象类能常代表一个抽象概念,它提供一个继承的出发点,当设计一个新的抽象类时,一定是用来继承的,所以,在一个以继承关系形成的等级结构里面,树叶节点应当是具体类,而树枝节点均应当是抽象类。
也就是说,具体类不是用来继承的。我们作为编程设计者,应该要努力做到这一点。
(参见图示)
A.11 接口
接口interface。接口是把隐式公共方法和属性给合起来,以封装特定功能的一个集合。一旦类实现了接口,类就可以支持接口所指定的所有属性和成员。声明接口在语法上与声明抽象类完全相同,但不允许提供接口中任何成员的执行方式。
所以接口不能实例化,不能有构造方法和字段;不能有修饰符,比如public、private等;不能声明虚拟的或静态的等。还有实现接口的类就必须要实现接口中的所有方法和属性。
一个类可以支持多个接口,多个类也可以支持相同的接口。所以接口的概念可以让用户和其它开发人员更容易理解其它人的代码。
记住,接口的命名,前面要加一个大写字母‘I’,这是规范。
从表象上来说,抽象类可以给出一些成员的实现,接口却不包含成员的实现,抽象类的抽象成员可被子类部分实现,接口的成员需要实现类完全实现,一个类只能继承一个抽象类,但可实现多个接口等等。但这些都从两者的形态上去区分的。
有三点能帮助我们去区分抽象类和接口的。
第一, 类是对对象的抽象;抽象类是对类的抽象;接口是对行为的抽象。
接口是对类的局部(行为)进行的抽象,而抽象类是对类整体(字段、属性、方法)的抽象。如果只关注行为抽象,那么也可以认为接口就是抽象类。总之,不论是接口、抽象类、类甚至对象,都是在不同层次、不同角度进行抽象的结果,它们的共性就是抽象。
第二, 如果行为跨越不同类的对象,可使用接口;对于一些相似的类对象,用继承抽象类。
比如猫呀狗呀它们其实都是动物,它们之间有很多相似的地方,所以我们应该让它们去继承动物我个抽象类,而飞机、麻雀、超人是完全不相关的类,叮当是动漫角色,孙悟空是古代神话人物,这也是不相关的类,但它们又有共同点的,前三个都会飞,而后两个都会变出东西,所以此时让它们去实现相同的接口来达到我们的设计目的就很合适了。实现接口和继承抽象类并不冲突。完全可以让超人继承人类,再实现飞行接口。
第三, 从设计角度讲,抽象类是从子类中发现了公共的东西,泛化出父类,然后子类继承父类,而接口是根本不知子类的存在,方法如何实现还不确认,预先定义。
这里其实说明的是抽象类和接口设计的思维过程。
回想一下我们刚讲的时候,先是有一个Cat类,然后再有一个Dog类,观察后发现它们有类似之处,于是泛化出Animal类,这也体现了敏捷开发的思想,通过重构改善既有的代码的设计。所以说抽象类往往都是通过重构得来的,当然,如果你事先意识到多种分类的可能,那么事先就设计出抽象类也是完全可以的。而接口就完全不是一回事,比如我们是动物运动会的主办方,在策划时,大家就坐在一起考虑需要组织什么样的比赛,而此时,主办方其实还不太清楚会有什么样的动物来参加运动会,所有的这些比赛项目都可能是完全不相同的动物在比,它们将如何去实现这些行为也不得而知,此时,能做的事就是事先定义这些比赛项目的行为接口。
“抽象类是自底而上抽象出来的,而接口则是自顶向下设计出来的”,可以这么说,但仅仅理解这一点是不够的,要想真正把抽象类和接口用好,还是需要好好用心地去学习设计模式。只有真正把设计模式理解好了,那么你才能算是真正会合理应用抽象类和接口了。
A 12 集合
数组优点:数组在内存中连续存储,因此可以快速而容易地从头到尾遍历元素,可以快速修改元素等等。
缺点,在创建时必须要指定数组变量的大小,还有在两个元素之间添加元素也比较困难。
数组长度设置过大,造成内存空间浪费,长度设置过小造成溢出。所以.NET Framework提供了用于数据存储和检索的专用类,这些类统称集合。这些类提供对堆栈、队列、列表和哈希表的支持。大多数集合类实现相同的接口。
我们介绍当中最常用的一种ArrayList。
ArrayList是命名空间System.Collections下的一部分,它是使用大小可按需动态增加的数组实现Ilist接口。Ilist接口定义了很多集合用的方法,ArrayList的容量是ArrayList可以保存的元素数。ArrayList的默认初始容量为0。随着元素添加到ArrayList中,容量会根据需要通过重新分配自动增加。使用整数索引可以访问此集合中的元素。此集合中的索引从零开始。