面向对象基础知识

最近入手程杰的《大话设计模式》,正如书中所说,小菜还需努力,顺便记录下面相对象的基础知识

封装的概念

对于一个具有丰富结构化程序设计经验的程序员来说,面向对象的程序设计可能会给他们带来非常不自然的感觉。封装是实现面向对象程序设计的第一步,封装就是将数据或函数等集合在一个个的单元中(我们称之为类)。被封装的对象通常被称为抽象数据类型。在本文中,我们将详细学习属性的特性。

封装的意义

一: 使用者只需要了解如何通过类的接口使用类,而不用关心类的内部数据结构和数据组织方法。
二:高内聚,低耦合一直是我们所追求的,用好封装恰恰可以减少耦合
三:只要对外接口不改变,可以任意修改内部实现,这个可以很好的应对变化
四:类具有了简洁清晰的对外接口,降低了使用者的学习过程

代码的实现

1.类的封装
首先和书上同样要召开以此动物大会的歌唱大赛,猫小姐首先代表”猫科“动物表演,我们定义一个歌唱的方法

public string Shout ()
{
return “喵”;
}
控制台调用
Console.WriteLine(Shout());
这时万一别的地方的猫小姐也要表演调用Shout方法,就不可以了,需封装到一个类里(类就是具有相同的属性和功能的对象抽象的集合
Class Cat
{
public string Shout ()
{
return “喵”;
}
}
调用
Cat cat = new Cat();//实例类的对象
Console.WriteLine(cat.Shout());

2. 构造方法
下面要每个唱歌的小姐们起名字,就需要在实例化是构造方法(所有类都有构造方法,如果不编码则系统会默认成空的构造方法,若你有定义好的构造方法,那么默认的构造就会失效了
private string name = “”;//字段
public Cat(string name) //构造方法
{
this.name = name;
}
如需歌手唱完歌后在起名,也就是实例化后在给予名称需要用到重载(函数重载是指同一个函数名可以对应着多个函数的实现。每种实现对应着一个函数体,这些函数的名字相同,但是函数的参数的类型不同。这就是函数重载的概念。函数重载在类和对象的应用尤其重要),这是类实例化是会根据形参来调用需要的方法
public Cat() //重载构造
{
this.name = “无名”;
}

3.属性与修饰符
如果要控制歌手们曲目数,就需要用到属性 (属性是一个方法或一对方法,但在调用它的代码来看,它是一个字段,即属性适合于以字段的方式使用方法的调用场合

private int shoutNum = 3;//字段动物表演的次数

public int ShoutNum //属性
{
get { return shoutNum; }
set
{
if (value <= 10)
{
shoutNum = value;
}
else
{
shoutNum = 10;
}
}
}

4:代码

public class Cat
{
private int shoutNum = 3;//动物叫的次数

public int ShoutNum //属性
{
get { return shoutNum; }
set
{
if (value <= 10)
{
shoutNum = value;
}
else
{
shoutNum = 10;
}
}
}
private string name = "";//字段
public Cat(string name) //构造方法
{
this.name = name;
}
public Cat() //重载构造
{
this.name = "无名";
}
public string Shout()
{
string result = "";
for (int i = 0; i < shoutNum; i++)
{
result += "";
}
return "我的名字叫" + this.name + " " + result;
}
}


继承的概念

继承是面向对象程序设计的主要特征之一,它可以让您重用代码,可以节省程序设计的时间。继承就是在类之间建立一种相交关系,使得新定义的派生类的实例可以继承已有的基类的特征和能力,而且可以加入新的特性或者是修改已有的特性建立起类的新层次

概念特性:
1.子类拥有父类非Private的属性与功能
2.子类具有自己的属性与功能,即子类可以扩展父类没有的属性和功能
3.子类还可以以自己的方式实现父类的功能(方法的重写 override)

继承的意义

继承使得所有子类公共的部分都放在父类,使得代码得到共享,避免代码的重复,可使得修改或扩展继承而来的实现都比较容易,从而提高软件模块的可复用性和可扩充性,以便提高软件的开发效率,我们总是希望能够利用前人或自己以前的开发成果,同时又希望在自己的开发过程中能够有足够的灵活性,不拘泥于复用的模块。C#这种完全面向对象的程序设计语言提供了两个重要的特性–继承性inheritance 和多态性polymorphism。

代码的实现

1.类的封装
上文说到动物们参加比赛,以封装好Cat与Dog 类,我们会发现者两个类有很多相似的代码(构造方法 为动物起名,属性shouNum 控制表演的次数),只是Cat 与Dog 表演发出的声音不同,这样我们就可以利用面向对象继承的特性对相同代码进行再次封装成一个Animal 类,让Dog与Cat类继承与Animal 类

public class Animal
{
protected string name = “”;//字段 动物的名称
public Animal(string name)//有参构造
{
this.name = name;
}
public Animal()//无参构造
{
this.name = “无名”;
}
private int shoutNum = 3;//字段控制叫的次数

protected int ShoutNum
{
get { return shoutNum; }
set { shoutNum = value; }
}
}

2. Base 关键字与this 关键字

Base关键字用于从派生类中访问基类的成员:

  • 调用基类上已被其他方法重写的方法。
  • 指定创建派生类实例时应调用的基类构造函数。

基类访问只能在构造函数、实例方法或实例属性访问器中进行(从静态方法中使用 base 关键字是错误的)

例:在派生类中调用基类方法

/ base 关键字
// 访问基类成员
using System;

public class BaseClass
{
protected string _className = "BaseClass";

public virtual void PrintName()
{
Console.WriteLine("Class Name: {0}", _className);
}
}

class DerivedClass : BaseClass
{
public string _className = "DerivedClass";

public override void PrintName()
{
//调用基类方法
base.PrintName();
Console.WriteLine("This DerivedClass is {0}", _className);
}
}

class TestApp
{
public static void Main()
{
DerivedClass dc = new DerivedClass();
dc.PrintName();
}
}

/**//*
控制台输出:
The BaseClass Name is BaseClass
This DerivedClass is DerivedClass
*/


例:在派生类中调用基类构造函数

public class Cat : Animal//继承Animal
{
public Cat()//无参继承构造
: base()
{
}
public Cat(string name) //有参继承构造
: base(name)
{
}

this关键字引用类的当前实例限定被相似的名称隐藏的成员,将对象作为参数传递到其他方法
如:

 private string _name;
private int _age;
private string[] _arr = new string[5];

public Employee(string name, int age)
{
// 使用this限定字段,name与age
this._name = name;
this._age = age;
}

public string Name
{
get { return this._name; }
}

public int Age
{
get { return this._age; }
}

// 打印雇员资料
public void PrintEmployee()
{
// 将Employee对象作为参数传递到DoPrint方法
Print.DoPrint(this);
}

.实现继承
这里要有一个概念 :派生类从它的直接基类中继承成员:方法、域、属性、事件、索引指示器。除了构造函数和析构函数,派生类隐式地继承了直接基类的所有成员,对于构造方法,有一些特殊,他不能被继承,只能被调用,对于调用父类的成员,可以用Base 关键字

Cat类:
public class Cat : Animal//继承Animal
{
public Cat()//无参继承构造
: base()
{
}
public Cat(string name) //有参继承构造
: base(name)
{
}
public string Shout()
{
string result = "";
for (int i = 0; i &lt; ShoutNum; i++)
{
result += "";
}
return "My name is " + name + " " + result;
}

Dog类:
public class Dog : Animal
{
public Dog()//无参继承构造
: base()
{
}
public Dog(string name) //有参继承构造
: base(name)
{
}
///
/// 表演的方法
///
///
public string Shout()
{
string result = "";
for (int i = 0; i &lt; ShoutNum; i++) //ShoutNum 为父类属性
{
result += "";
}
return "My name is " + name + " " + result;
}

3.控制台调用
Cat cat = new Cat();
Console.WriteLine(cat.Shout());
Dog dog = new Dog("旺财");//有参构造
Console.WriteLine(dog.Shout());

//Animal animal = new Cat("Kity");
Console.Read();

 

继承规则

1、继承是可传递的。如果C从B中派生,B又从A中派生,那么C不仅继承了B中声明的成员,同样也继承了A中的成员。Object 类作为所有类的基类。
2、派生类应当是对基类的扩展。派生类可以添加新的成员,但不能除去已经继承的成员的定义。
3、构造函数和析构函数不能被继承。除此以外的其它成员,不论对它们定义了怎样的访问方式,都能被继承。基类中成员的访问方式只能决定派生类能否访问它们。
4、派生类如果定义了与继承而来的成员同名的新成员,就可以覆盖已继承的成员。但这并不因为这派生类删除了这些成员,只是不能再访问这些成员。
5、类可以定义虚方法、虚属性以及虚索引指示器,它的派生类能够重载这些成员,从而实现类可以展示出多态性。
6、派生类只能从一个类中继承,可以通过接吕实现多重继承。

继承的概念

多态可以简单的理解为对不同的对象调用相同的方法,表现出不同的行为,这种特性是通过继承来实现的,同一操作作用于不同的对象,可以有不同的解释,产生不同的执行结果,这就是多态性。多态性通过派生类重载基类中的虚函数型方法来实现。

 

 

多态的概念

多态可以简单的理解为对不同的对象调用相同的方法,表现出不同的行为,这种特性是通过继承来实现的,同一操作作用于不同的对象,可以有不同的解释,产生不同的执行结果,这就是多态性。多态性通过派生类重载基类中的虚函数型方法来实现。

多态述说

先来看一个简单的例子,展现一下多态的实现。

public class Animal
{
public virtual void Shout()
{
Console.WriteLine("Animal Shout");
}
}

public class Cat : Animal
{
public override void Shout()
{
Console.WriteLine("Cat Shout");
}
}

public class Dog : Animal
{
public override void Shout()
{
Console.WriteLine("Dog Shout");
}
}

控制台调用
Animal[] animals = new Animal[3];

animals[0] = new Animal();
animals[1] = new Cat();
animals[2] = new Dog();

for (int i = 0; i &lt; 3; i++)
{
animals[i].Shout();
}

说明理解:
1.在上面的例子中,为了使子类完全接替父类成员 ,父类中Shout()方法需为virtual(虚) 方法,子类需继承父类并override (重写)了父类的方法,替换为自己的实现,通过继承,使得Animal对象数组中的不同的对象,在调用Shout()方法时,表现出了不同的行为。

2.上面的多态实例表示不同的对象可以执行相同的动作,但要通过它们自己的实现代码来执行, 引用《大话设计模式》中的一个故事来说明下,国粹“京剧”的一家族,父亲是名有名的京剧演员,儿子成人以后呢,模仿父亲也惟妙惟肖,一日,父亲生病不能出演,由于京剧都是化妆后再出演的 ,儿决定代父演出

2.1 子类以父类的身份出演,儿代码出演,化妆后就以父亲的身份出现,如上文中Cat类中的Shout()方法要代替Animal类中Shout()方法来驱动
2.2 子类在工作时以自己的方式来实现,儿只能以自己的理解方式去模仿父亲的作品,如上文Cat类中的Shout()实现了父类Animal类中Shout()方法
2.3 子类以父类的身份出现时,子类特有的属性和方法不能使用,儿代表父亲出演时自己的绝活不能表现出来

new关键字的用法

public class Animal
{
public virtual void Eat()
{
Console.WriteLine("Animal Shout");
}
}

public class Cat : Animal
{
public new void Shout()
{
Console.WriteLine("Cat Shout");
}
}

控制台调用
Animal a = new Animal();
a.Shout();

Animal ac = new Cat();
ac.Shout();

Cat c = new Cat();
c.Shout();

可以看出,当派生类Cat的Shout()方法使用new修饰时,Cat的对象转换为Animal对象后,调用的是Animal类中的Shout()方法。其实可以理解为,使用new关键字后,使得Cat中的Shout()方法和Animal中的Shout()方法成为毫不相关的两个方法,只是它们的名字碰巧相同而已。所以, Animal类中的Shout()方法不管用还是不用virtual修饰,也不管访问权限如何,或者是没有,都不会对Cat的Eat()方法产生什么影响(只是因为使用了new关键字,如果Cat类没用从Animal类继承Shout()方法,编译器会输出警告)。
我想这是设计者有意这么设计的,因为有时候我们就是要达到这种效果。严格的说,不能说通过使用new来实现多态,只能说在某些特定的时候碰巧实现了多态的效果。

override实现多态

真正的多态使用override来实现的。回过去看前面的例1,在基类Animal中将方法Eat()用Shout标记为虚拟方法,再在派生类Cat和Dog中用override对Eat()修饰,进行重写,很简单就实现了多态。需要注意的是,要对一个类中一个方法用override修饰,该类必须从父类中继承了一个对应的用virtual修饰的虚拟方法,否则编译器将报错。
好像讲得差不多了,还有一个问题,不知道你想没有。就是多层继承中又是怎样实现多态的。比如类A是基类,有一个虚拟方法method()(virtual修饰),类B继承自类A,并对method()进行重写(override修饰),现在类C又继承自类B,是不是可以继续对method()进行重写,并实现多态呢?

public class Animal
{
public virtual void Shout()
{
Console.WriteLine("Animal Shout");
}
}

public class Dog : Animal
{
public override void Shout()
{
Console.WriteLine("Dog Shout");
}
}

public class WolfDog : Dog
{
public override void Shout()
{
Console.WriteLine("WolfDog Shout");
}
}
控制台调用
Animal[] animals = new Animal[3];

animals[0] = new Animal();
animals[1] = new Dog();
animals[2] = new WolfDog();

for (int i = 0; i &lt; 3; i++)
{
animals[i].Shout();
}

在上面的例子中类Dog继承自类Animal,对方法Eat()进行了重写,类WolfDog又继承自Dog,再一次对Eat()方法进行了重写,并很好地实现了多态。不管继承了多少层,都可以在子类中对父类中已经重写的方法继续进行重写,即如果父类方法用override修饰,如果子类继承了该方法,也可以用override修饰,多层继承中的多态就是这样实现的。要想终止这种重写,只需重写方法时用sealed关键字进行修饰即可

abstract-override实现多态

先在我们在来讨论一下用abstract修饰的抽象方法。抽象方法只是对方法进行了定义,而没有实现,如果一个类包含了抽象方法,那么该类也必须用abstract声明为抽象类,一个抽象类是不能被实例化的。对于类中的抽象方法,可以再其派生类中用override进行重写,如果不重写,其派生类也要被声明为抽象类。看下面的例子。

public abstract class Animal
{
}

public class Cat : Animal
{
public override void Shout()
{
Console.WriteLine("Cat Shout");
}
}

public class Dog : Animal
{
public override void Shout()
{
Console.WriteLine("Dog Shout");
}
}

public class WolfDog : Dog
{
public override void Shout()
{
Console.WriteLine("Wolfdog Shout");
}
}
控制台调用
Animal[] animals = new Animal[3];

animals[0] = new Cat();
animals[1] = new Dog();
animals[2] = new WolfDog();

for (int i = 0; i &lt; animals.Length; i++)
{
animals[i].Shout();
}

可以看出,通过使用abstract-override可以和virtual-override一样地实现多态,包括多层继承也是一样的。不同之处在于,包含虚拟方法的类可以被实例化,而包含抽象方法的类不能被实例化。

重构的概念

查了下MSDN    链接 里面有些实例
重构是在编写代码后在不更改代码的外部行为的前提下通过更改代码的内部结构来改进代码的过程。或者说重构(Refactoring)就是在不改变软件现有功能的基础上,通过调整程序代码改善软件的质量、性能,使其程序的设计模式和架构更趋合理,提高 软件的扩展性和维护性

 

重构述说

一、为什么要重构(Refactoring)
1、·持续偏纠和改进软件设计。
2、·使代码更易为人所理解。
3、·帮助发现隐藏的代码缺陷。
4、·从长远来看,有助于提高编程效率。
二、何时着手重构(Refactoring)
1、·代码中存在重复的代码。
2、·过大的类和过长的方法。
3、·牵一毛而需要动全身的修改。
4、·类之间需要过多的通讯。
5、·过度耦合的信息链。
6、·不完美的设计。
7、·缺少必要的注释。
三、如何来重构(Refactoring)
3.1   Rename: 改名了,类、函数、成员等名字都可以改。改成大家容易理解其功能用途的名字:

如续上文中Animal 类 与Cat 类等,或者方法的名称

protected virtual string GetShoutSound()//获取声音的方法
{
return “”;
}

3.2  Encapsulate Field:    将某个字段转成属性

如文中的控制动物表演的次数
private int shoutNum = 3;//属性控制表演的次数
public int ShoutNum
{
get { return shoutNum; }
set
{
if (shoutNum <= 10)
{
shoutNum = value;
}
else
{
shoutNum = 10;
}
}
}

3.2   Extract Method: 将某段代码封到一个新函数里。如果在某个方法里有一段代码是可以重新用一个方法来封装的
提取方法是最常用的重构之一。当一个方法看起来太长或者方法中一些代码需要注释才能明白它的目的时,可以考虑把它们提取出来作为一个独立的方法如多态一文中的Cat类与Dog类中,都有一个重写基类的Shout ()方法,如果这是再有其他的动物来表演的话,那么必须都要继承与Animal 基类,会造成Shout 方法多次重复使用,这时可以用到重构的一个特性,把Shout方法,封装到基类Animal 类中,然后,设置一个获取动物声音的GetShoutSound虚方法,

后所有子类继承与Animal父类中,重写父类中的GetShoutSound即可

View Code
   {

private int shoutNum = 3;//属性控制表演的次数
public int ShoutNum
{
get { return shoutNum; }
set
{
if (shoutNum &lt;= 10)
{
shoutNum = value;
}
else
{
shoutNum = 10;
}
}
}

protected string name = "";//字段 动物的名称
public Animal(string name)//有参构造
{
this.name = name;
}
public Animal()//无参构造
{
this.name = "无名";
}
///
/// 表演的方法
///
///
public string Shout()
{
string result = "";
for (int i = 0; i &lt; ShoutNum; i++) //ShoutNum 为父类属性
{
result += GetShoutSound() + " ,";
}
return "My name is " + name + " " + result;
}
///
/// 虚方法获取声音
///
///
protected virtual string GetShoutSound()
{
return "";
}
}

//Cat类
public class Cat : Animal
{
public Cat()
: base()
{

}
public Cat(string name)
: base(name)
{

}
protected override string GetShoutSound()
{
return "";
}
}

控制台调用
Animal animal = new Cat("咪咪");
animal.ShoutNum = 5;//设置表演次数
Console.WriteLine(animal.Shout());
//首先调用父类中的Shout方法
//Cat 类中GetShoutSound() 方法重写了 父类中的GetShoutSound()方法
Console.ReadKey();

3.3 Extract Interface:    将某个属性或函数转成一个接口,从而是当前的这个属性或函数成为接口实现。

接口是把隐式的公共方法和人属性组合起来,以封装特定的一个集合,一旦实现了接口类就可以支持接口指定所有属性和成员 ,其实,接口简单理解就是一种约定,使得实现接口的类或结构在形式上保持一致。个人觉得,使用接口可以使程序更加清晰和条理化,这就是接口的好处,但并不是所有的编程语言都支持接口

声明接口

声明接口在语法上和声明抽象类完全相同,接口用interface,名字前一般加”I”,接口的方法或属性不能有修饰符方法没有方法体

接口的相关陈述
1.一个接口定义了一个契约。
2.接口可以包容方法、C#属性、事件、以及索引器(不包括委托)。
3.在一个接口声明中,我们可以声明零个或者多个成员。
4.所有接口成员的默认访问类型都是public。
5.如果在接口成员声明中包括了任何修饰符,那么会产生一个编译器错误。
6.与一个非抽象类类似,一个抽象类必须提供接口中所有成员的实现,只要这些成员在这个类的基类中出现过。
如前文所述,猫可有许多种类,需要特殊的猫来实现特定的动作,如果用多态,但有的行为只有一些猫会,父类中不可能全部实现这些行为,这是就需要一个接口
例:
public interface IChange
{
string ChangeThing(string thing);
}
注意:接口中只能包含方法(没有方法体)、属性、索引器和事件的声明。不允许声明成员上的修饰符,即使是pubilc都不行,因为接口成员总是公有的,也不能声明为虚拟和静态的。如果需要修饰符,最好让实现类来声明。

使用接口

如现在又一个机器猫要实现变东西的行为,但别的猫并不能实现这个功能,这时就不能用多态,实现父类的所有动作,子类并不能实现父类的行为,这样设计就不合理,这时就需要机器猫(MachineCat)的类来实现IChange 的接口

例:机器猫变东西实现IChange接口
class MachineCat : Cat, IChange
{
public MachineCat()
: base()
{
}

public MachineCat(string name)
: base(name)
{
}

public string ChangeThing(string thing)
{
return base.Shout() + ” 我有万能的口袋,我可变出” + thing;
}
}

机器猫继承与猫,并实现IChange 接口来实现自己的独有的行为

父类Cat:

class Cat : Animal
{
public Cat()
: base()
{ }

public Cat(string name):base(name)
{
}

protected override string getShoutSound()
{
return “喵”;
}
}

View Code
//动物父类Animal :
abstract class Animal
{
string name = "";
protected Animal(string name)
{
this.name = name;
}

public Animal()
{
this.name = "无名";
}

int shoutNum = 3;
public int ShoutNum
{
get
{
return shoutNum;
}
set
{
sho

控制台调用

MachineCat cat = new MachineCat(“机器猫”);
IChange change = cat;
Console.WriteLine(change.ChangeThing(“Many things”));
StoneMonkey monkey = new StoneMonkey(“嘻哈猴”);

接口和抽象类很相似,如果先在再有一个机器狗来实现变东西的行为,父类Dog类中不能实现这一行为,这时就有了2个对象,由于现在由两个不相干对象来做同样的事情”变东西”,所以不得让它们实现接口的对象来做出反应,当调用接口“变东西的方法”时,就需要根据接口的对象来做出反应,做出不同的行为


 


 


posted @ 2011-10-11 11:20  流逝在夏天  阅读(599)  评论(0)    收藏  举报