面向对象的主要思想
-
-分而治之
-
高内聚,低耦合
-
封装变化(细节)
面向对象的设计原则
-
开-闭原则 (目标) :对扩展开放,对修改关闭
-
类的单一职责(一个类的定义):一个类有且只有一个改变它的原因, 易复用,易维护!
-
面向接口编程而非面向实现 (写一个类时从哪入手):先做好一个好对外的接口(公有的方法),实现不是第一步要思考.先思考做什么,为其他的组件提供什么功能,而不是去先思考功能的实现
-
使用组合而非继承 (复用的最佳实践):如果仅仅为了复用优先选择组合复用,而非继承复用,组合的耦合性对继承低.
-
依赖倒置 ( 依赖抽象):客户端代码尽量依赖抽象的组件,因为抽象的是稳定的。实现是多变的。
-
里氏替换 (继承后的重写):父类出现地方可以被子类替换掉。要保护替换前的原有的工作,在替换后依然保持不变,子类在重写父类方法时,尽量选择扩展重写。
-
接口隔离 (功能拆分): 尽量定义小而精的接口,少定义大而全的接口 ,小接口之间功能隔离,实现类需要多个功能时可以选择多实现.或接口之间做继承
-
迪米特法则 (类与类交互的原则) : 不要和陌生人说话【原则】,类与类交互时,在满足功能要求的基础上,传递的数据量越少越好.因为这样可能降低耦合度.
面向对象的三大特性 C#
- 封装: 如何组织类或模块,让封装的类或组件,尽量只负责一个领域的工作.
- 继承: 复用方式之一,概念形成统一。通过继承可以管理多个概念
- 多态: 一个行为不同的做法。目标一致,实现的方式不同
封装: 如何组织类或模块,让封装的类或组件,尽量只负责一个领域的工作
==类与类的四大关系 ==
-
泛化: 类与类的继承关系。耦合度最高
-
实现:实现类与接口之间的关系。
-
关联: 整体与部分的关系,另一个类作为当前类的成员出现
组合 Person{ id,name,Head,Leg} -
依赖: 类与类的协作关系,另一个类型作为当前类型的方法的参数或返回值类型出现
Class A{ Test(Course c)}
继承
实现继承和接口继承
实现继承:表示一个类型派生于基类型,它拥有该基类型的所有成员字段和函数。在实现继承中,派生类型采用基类型的每个函数的实现代码,除非在派生类型的定义中指定某个函数的实现代码。在需要给现有的类型添加功能,或许多相关的类型共享一组重要的公共功能时,可以使用这种类型的继承。
接口继承:表示一个类型只继承了函数的签名,没有继承任何的代码。在需要指定该类型具有某些可用的特性时,最好使用这种类型的继承。
2.多重继承
C#不支持多重继承,但C#允许类型派生自多个接口————多重接口继承。这说明,C#类可以派生自另一个类和任意多个接口。更准确的说,因为System.Object是一个公共的基类,所以每个C#(除Object之外)都有一个基类,还可以有任意多个接口。
继承的实现
语法:
class MyDreved:BaseClass
{
}
如果类或结构也派生自接口,则用逗号分隔列表中的基类和接口:
class MyDreved:BaseClass,IIntenface1,IIntenface2
{
}
如果在类定义中没有指定基类,C#编译器就假定System.Object是基类。
1.虚方法
把一个基类函数声明为virtual,就可以在任何派生类中重写(override)该函数:
class BaseClass
{
public virtual void VirtualMethod()
{
//
}
}
也可以把属性声明为virtual。对于虚属性或重写属性,语法与非虚属性相同,但要在定义中添加virtual关键字:
public virtual string Name
{
get;set;
}
C#中虚函数的概念与标准OOP的概念相同:可以在派生类中重写虚函数。在调用方法时,会调用该派生类的合适方法。在C#中,函数默认情况下不是虚的,但(除了构造函数)可以显式的声明为virtual。
在派生类中重写一个函数时,要使用override关键字显示声明:
class MyDreved: BaseClass
{
public override void VirtualMethod()
{
//
}
}
成员字段和静态函数都不能声明为virtual,因为这个概念只对类中的实例函数成员有意义。
2.隐藏方法
如果签名相同的方法在基类和派生类中都进行了声明,但该方法没有分别声明为virtual和override,派生类方法就会隐藏基类方法。
class A
{
public void a()
{
Console.WriteLine('CLASS is A');
}
}
class B:A
{
public void a()
{
Console.WriteLine('CLASS is B');
}
}
class client
{
static void main()
{
B b=new B();
A a=b;
a.a();
b.a();
}
}
/*输出
CLASS IS A
CLASS IS B
*/
在大多数情况下,是要重写方法,而不是隐藏方法,因为隐藏方法会造成对于给定类的实例调用错误的方法。但是,C#语法会在编译时收到这个潜在错误的警告。
在C#中,要隐藏一个方法应使用new 关键字声明,这样在编译时就不会发出警告:
class A
{
public void a()
{
Console.WriteLine('CLASS is A');
}
}
class B:A
{
public new void a()
{
Console.WriteLine('CLASS is B');
}
}
3.调用函数的基类版本
C#可以从派生类中调用方法的基本版本:base.()
class MyDreved: BaseClass
{
public override void VirtualMethod()
{
base.VirtualMethod();
}
}
可以使用base.()语法调用基类中的任何方法,不必从同一方法的重载中调用它。
4.抽象类和抽象函数
C#允许把类和函数声明为abstract.抽象类不能实例化,而抽象不能直接实现,必须在非抽象的派生类中重写。显然抽象函数也是虚拟的(尽管不需要提供virtual,实际上,也不能提供该关键字)。
如果类包含抽象函数,则该类也是抽象的,也必须声明为抽象的:
abstract class Building
{
public abstract void Cal();
}
抽象类中不能声明非抽象方法,但可以声明其它的非抽象成员。
5.密封类和密封方法
C#允许把类和方法声明为sealed。对于类,这表示不能继承该类;对于方法,表示不能重写该方法。
sealed class A
{
}
class B:A //报错
{
}
如果基类上不希望有重写的方法和属性,就不要把它声明为virtual.
6.派生类的构造函数
构造方法不会继承给子类,但是在创建子类对象时,自动调用父类的构造方法,且父类构造方法先执行,子类构造方法后执行.
当子类采用无参构造方法创建对象时,默认调用父类的无参构造方法,如果父类没有无参构造方法,则报编译错误,解决方法有两个:
1.为父类添加无参构造方法,
2.在子类的构造方法中用base关键字指明要调用父类的哪一个有参构造方法
class Program
{
static void Main(string[] args)
{
Man man = new Man("构造函数");
//结果仍然是先输出父类构造函数,然后再输出子类构造函数
}
}
public class People
{
public People(string s)
{
Console.WriteLine("父类"+s);
}
}
class Man:People
{
//在继承时,如果基类构造函数是有参数的,子类构造函数也必须有一个有参数的构造函数,否则会报错
public Man(string s):base(s)
{
Console.WriteLine("子类构造函数");
}
}
修饰符
修饰符可以指定方法的可见性:如public或private,还可以指定一项的本质,如方法是virtual或abstract.
1.可见性修饰符
修饰符 应用于 说明
public 所有类和成员 任何代码可以访问
protected 类的成员和内嵌类 只有在类内部和派生类中访问
internal 所有类和成员 只有在类内部和包含它的程序集中访问
private 类的成员和内嵌类 只有在类内部访问
protected internal 类的成员和内嵌类 只有在类内部,派生类中和包含它的程序集中访问
不能把类定义为protected,private,protected internal,因为这些修饰符对于包含在名称空间中的类型没有意义。因此这些修饰符只能应用于成员。但是可以用这些修饰符定义嵌套的类(内嵌类,包含在其它类中的类),因为在这种情况下,类也具有成员的状态:
public class OuterClass
{
protected class InnerClass
{
}
}
2.其它修饰符
修饰符 应用于 说明
new 函数 隐藏函数
static 所有成员 静态
virtual 函数 成员可以由派生类重写
abstract 类,函数 抽象
override 函数 重写虚拟和抽象的成员
sealed 类,方法,属性 不能继承和重写
extern 仅静态方法 成员在外部用另一种语言实现
多态
怎么理解多态
多态:多种形态,对象的多种形态,行为的多种形态
体现为,子类对象可以通过父类型引用,行为的多态体现为方法的重写,隐藏,重载
继承将相关概念的共性进行抽象,并提供了一种复用的方式。
多态在共性的基础上,体现类型及行为的个性化。一个行为有多个不同的实现
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()方法时,表现出了不同的行为。
1. new的用法
先看下面的例子。
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()进行重写,并实现多态呢?看下面的例子。
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进行重写,如果不重写,其派生类也要被声明为抽象类。看下面的例子。
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一样地实现多态,包括多层继承也是一样的。不同之处在于,包含虚拟方法的类可以被实例化,而包含抽象方法的类不能被实例化。