C#面向对象

面向对象的主要思想

  • -分而治之

  • 高内聚,低耦合

  • 封装变化(细节)

面向对象的设计原则

  1. 开-闭原则 (目标) :对扩展开放,对修改关闭

  2. 类的单一职责(一个类的定义):一个类有且只有一个改变它的原因, 易复用,易维护!

  3. 面向接口编程而非面向实现 (写一个类时从哪入手):先做好一个好对外的接口(公有的方法),实现不是第一步要思考.先思考做什么,为其他的组件提供什么功能,而不是去先思考功能的实现

  4. 使用组合而非继承 (复用的最佳实践):如果仅仅为了复用优先选择组合复用,而非继承复用,组合的耦合性对继承低.

  5. 依赖倒置 ( 依赖抽象):客户端代码尽量依赖抽象的组件,因为抽象的是稳定的。实现是多变的。

  6. 里氏替换 (继承后的重写):父类出现地方可以被子类替换掉。要保护替换前的原有的工作,在替换后依然保持不变,子类在重写父类方法时,尽量选择扩展重写。

  7. 接口隔离 (功能拆分): 尽量定义小而精的接口,少定义大而全的接口 ,小接口之间功能隔离,实现类需要多个功能时可以选择多实现.或接口之间做继承

  8. 迪米特法则 (类与类交互的原则) : 不要和陌生人说话【原则】,类与类交互时,在满足功能要求的基础上,传递的数据量越少越好.因为这样可能降低耦合度.

面向对象的三大特性 C#

  1. 封装: 如何组织类或模块,让封装的类或组件,尽量只负责一个领域的工作.
  2. 继承: 复用方式之一,概念形成统一。通过继承可以管理多个概念
  3. 多态: 一个行为不同的做法。目标一致,实现的方式不同

封装: 如何组织类或模块,让封装的类或组件,尽量只负责一个领域的工作
==类与类的四大关系 ==

  1. 泛化: 类与类的继承关系。耦合度最高

  2. 实现:实现类与接口之间的关系。

  3. 关联: 整体与部分的关系,另一个类作为当前类的成员出现
    组合 Person{ id,name,Head,Leg}

  4. 依赖: 类与类的协作关系,另一个类型作为当前类型的方法的参数或返回值类型出现
    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一样地实现多态,包括多层继承也是一样的。不同之处在于,包含虚拟方法的类可以被实例化,而包含抽象方法的类不能被实例化。

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值