打开APP
userphoto
未登录

开通VIP,畅享免费电子书等14项超值服

开通VIP
面向对象设计(二)——对象间的关系引发的思考

1.1   结构体

    结构体是面向过程中的产物,应该说结构体是类的雏形,随着面向对象编程思想的完善,于是就产生了类,并且对类进行了充分的完善,而结构体则渐渐退出了历史舞台,但是它并没有遭到遗弃,因为它还有它的利用价值。结构体可以把功能相同的数据组织起来,存在一起,用是时候方便,而且在调用函数时,若传递参数较多,传一个结构体相对而言简单一些。

结构体与类在格式上没有什么区别,主要的区别在于类型。结构体的类型是值类型。而类的类型是引用类型。值类型指的是它的成员相关数据全部以真实数值的方式来存储,系统是按照真实数据的地址来寻找到结构体的数据。而引用类型实际上是一个指针存储的方式,它是把指向类中相关真实数据信息地址的指针存储起来了,系统是先找到指针,根据这个指针所指向的内存地址来去提取真实的数据信息。这样的问题是一旦出现复制,修改数据等情况时,两种的处理结果会截然不同。

下面就是一个例子,说明了值类型和引用类型的区别。

namespace ConsoleApplication1

{

    struct Carlist

    {

        public double Weight;

    }

    

    class Car :Class1

    {

        public double Weight

        {

            set;

            get;

        }

        protecteddouble OK

        {

            get;

            set;

        }

        public static void Start()

        {

            Console.WriteLine("?¤?é?¥¢?");

 

        }

        public void Writetext()

        {

            Class1class1 = new Class1();

            class1.height = 20;

           

            Console.WriteLine("??¨¨a" + class1.height);

        }

    }

    class Program

    {

        static void Main(string[]args)

        {

            CarWangWuCar = new Car();

           Car WangWuCar1 = WangWuCar;

            WangWuCar1.Weight = 10;

            WangWuCar.Weight = 20;

            CarlistLiCar=new Carlist();

            CarlistLiCar1 = LiCar;

            LiCar.Weight = 10;

            LiCar1.Weight = 20;

            Console.WriteLine(WangWuCar.Weight);

            Console.WriteLine(WangWuCar1.Weight);

            Console.WriteLine(LiCar.Weight);

            Console.WriteLine(LiCar1.Weight);

            CarLiSiCar = new Car();

            Car.Start();

            Class1.WriteText();

            Console.ReadKey();

        }

    }

}

 

这个例子里面,最终的运行的结果是2020,10,20。大家应该通过我上面的讲述可以理解是什么原因了。

但是现在又出现了一个问题,就是如果类的复制只是指针的复制而不是真实数值的复制,这样也不是我们想要的,那怎么办呢?.NET提出了一个深度复制的概念,它是通过ICloneable接口来实现如何用看下面的例子

 

 

namespace ConsoleApplication1

{

    struct Carlist

    {

        public double Weight;

    }

    

    class Car :Class1

    {

        public double Weight

        {

            set;

            get;

        }

        protecteddouble OK

        {

            get;

            set;

        }

        public static void Start()

        {

            Console.WriteLine("?¤?é?¥¢?");

 

        }

        public void Writetext()

        {

            Class1class1 = new Class1();

            class1.height = 20;

           

            Console.WriteLine("??¨¨a" + class1.height);

        }

    }

    class Program

    {

        static void Main(string[]args)

        {

            CarWangWuCar = new Car();

            CarWangWuCar1 = new Car();

            //CarWangWuCar1 = WangWuCar;

            //WangWuCar1.Weight= 10;

            //WangWuCar.Weight= 20;

            ICloneable.Equals(WangWuCar,WangWuCar1);

            WangWuCar1.Weight = 10;

            WangWuCar.Weight = 20;

            CarlistLiCar=new Carlist();

            CarlistLiCar1 = LiCar;

            LiCar.Weight = 10;

            LiCar1.Weight = 20;

            Console.WriteLine(WangWuCar.Weight);

            Console.WriteLine(WangWuCar1.Weight);

            Console.WriteLine(LiCar.Weight);

            Console.WriteLine(LiCar1.Weight);

            CarLiSiCar = new Car();

            Car.Start();

            Class1.WriteText();

            Console.ReadKey();

        }

    }

}

2            多个对象或类的分析

2.1 类或对象间的关系

上面分析的都是一个对象,我们不考虑对象间关系的情况下,对单个对象的分析和阐述。当我们考虑到多个对象或类的时候,问题就显的有些复杂了。总体来说,类,对象之间的关系有三种,分别是行为驱动,包含和泛化。下面详细讲讲这三种关系。

2.1.1   行为驱动关系

关联关系指的是一个类与另一类通过行为来构成的一定关系。比如说一个人开汽车,这句话说明了人是一类,汽车是另一类,这两类通过“开”这个行为联系起来,用UML图来表示如下:

通过上图我们可以清楚的看到PersonCar通过了Drive关联起来了。

 

2.1.2   包含关系

包含关系实际上是“Has A”的关系,包含的关系。这种包含带有分类的性质。比如人有男人,女人,这是按照性别来分类的,人有邮递员,程序员,经理,总裁,这是按照角色来分类的。比如汽车有奔驰,宝马,沃尔沃,这是按照品牌分类的。这种带有分类性质的包含关系不具备上下继承的性质。如果是用UML关系图表示的话,应该是如下模式:

2.1.3 泛化关系

泛化关系才是真正意义上的子呈父业的继承关系。将一些相关的类或者对象聚在一起,抽离出他们之间的共同部分,不变的部分,作为父类的内容,而子类可以继承父类的内容。为什么这样做?就是为了将不变的地方抽离出来,我们尽可能做到只改动极少的地方以满足变动的需求。这样我们的人力成本达到最低。

比如说动物里面有鸭子,猫,大象,猴子,这些都是动物,他们有共同点,就是都有体重,有身高,有性别,有一个嘴巴,有两只眼睛等等。那么就要创建一个父类,它具有体重,身高,性别,嘴巴数量,眼睛数量的属性。

 

 

2.1.4 聚合关系

      聚合关系指的是一个对象或者类是由多个部件组成的。聚合一般是由若干个小对象组成的。比如说汽车是由方向盘,发动机,轮胎等若干个对象组成的。在UML中是如下定义聚合关系的。

  

2.2   类或对象的关系范式

   上面我们分析了类或对象之间的基本关系,但是它存在类似数据库中的范式关系。也就是说他们之间存在着一对一,一对多,多对多的关系以及约束条件。

2.2.1   一对一的关系

     一对一指的是两个类或者对象是唯一对应关系。比如人只有一个脑袋,汽车只有一个方向盘,这就是一对一的关系。

2.2.2   一对多的关系

     一对多指的是一个类或者对象对应多个类或者对象的关系。比如公司可以拥有多名全职员工,而全职员工只能属于一家公司。

2.2.3   多对多的关系

     多对多指的是多个类或者对象对应多个类或对象的关系。比如一个人可以拥有多家公司的股票,而公司的股票也可以被多个人持有,这种就是多对多的关系。

2.2.4   约束条件

     我们在分析一对一,一对多,多对多的关系中,发现构成这些关系可能需要一些约束条件。比如公司拥有多名全职员工,这个全职就是对员工的约束条件,如果是兼职员工,那么公司和员工的关系就会变成多对多的关系了。诸如此类的限制条件还有很多,我们需要在进行系统设计的过程中仔细考虑这些问题。

 

2.3   对象或类的关系所引出概念的分析

我们前面分析了多个对象或者类的相关关系,那么如何用程序来实现这些关系呢?面向对象思想的设计者们,引入了诸如继承,多态,抽象类,虚方法等等丰富的概念和方法来充分实现上述的关系。

2.3.1   继承

     继承是多个类基于一种分层的关系,共享类间属性和操作。父类具备了众多子类中共有的属性和方法。具体内容上一节已经讲述了,在这里不再阐述。接下来讨论在程序上该如何实现子类和父类的继承关系。

     比如动物中有鸡和鸭,那么动物就是父类,狗和鸟就是子类,他们同样都具有吃饭,睡觉的功能,这就构成了父类具有的两个行为和方法。而鸟比狗多一个行为就是会飞,狗比鸟多一个会跑的功能。通过UML来实现这些类。

    

    用程序实现上述功能。

          public class Animal

    {

        public void Eat();

        public void Sleep();

     

    }

    public class Dog:Animal

    {

        public void Run();

    }

    public class Bird : Animal

    {

        public void Fly();

}

 

2.3.2 虚方法和虚属性

下面就会出现一个问题,我们发现狗和鸟吃饭的方式和内容肯定是不同的,动物类中的吃饭方式基本上就是抽象概念跟狗和鸟肯定也不同。因此我们想具体化狗和鸟吃饭的方式,那么我们需要在狗和鸟的类中重写吃饭这个方法。那么面向对象的方法就产生了虚函数,虚方法以及虚属性可以在派生类中重写父类也就是基类的方法。

具体方法是 把一个基类方法声明为virtual, 就可以在任何派生类中重写该方法,只不过需要在派生类中的重写方法前面加入override关键字就可以了。

public class Animal

    {

        public virtual void Eat()

        {

        }

        public void Sleep()

        {

        }

       

    }

    public class Dog:Animal

    {

        public void Run()

        {

        }

        public override void Eat()

        {

            base.Eat();

        }

    }

    public class Bird : Animal

    {

        public void Fly()

        {

        }

        public override void Eat()

        {

            base.Eat();

        }

}

上面的base.Eat()实际上说明了可以通过base.方法名或属性名的方式来调用基类的某些方法或者属性。

2.3.3 抽象类

     讲述完上面的问题后,又出现了一个问题,就是我们明明知道动物类是一个抽象的概念,其中的吃饭,睡觉也是一个极度抽象的概念,它无法具体化,因此面向对象的设计者们就提出了一个抽象类的概念,它只是一个极度抽象的概念,它不能实例化,只能被子类继承,为的就是让子类来将抽象类具体化。因此抽象类中的方法和属性必须要在派生类中重写,否则系统就会报错。这个抽象类只需要在类名前面加一个abstract前缀就可以了。我们将动物类改为abstract,具体的方法如下。

   public abstract class Animal

    {

        public abstract void Eat();

        public abstractvoid Sleep();

    }

    public class Dog:Animal

    {

        public void Run()

        {

        }

        public override void Eat()

        {

        }

    }

    public class Bird : Animal

    {

        public void Fly()

        {

        }

        public override void Eat()

        {

        }

    }

在这个抽象类中的方法都必须是抽象方法,并且在派生类中通过override来重写这些方法的。

2.3.4 密封类

密封类我理解实际上是软件公司为了保护自己的知识产权而想出的一个方法。这种密封类定义了之后,其他类是不能继承该类,并且密封类的方法也是不能重写的。所以该类完全被保护起来了。密封类很少使用,它的格式是在要密封的类名前面加一个sealed关键字就可以了。

2.3.5 多态

多态在面向对象的编程理念中指的是相同的方法有不同的实现方式,比如在中国象棋中的卒和国际象棋的卒都可以移动,但是移动的方式截然不同,同样的move方法用两种方式来实现它。

在这里我们想用一个变量来调用基类的方法该怎么办呢?可以看下面的例子

public  class Animal

    {

        public virtual void Eat()

        {

            Console.WriteLine("这是动物的吃饭方式o?");

        }

    }

    public class Dog:Animal

    {

        public void Run()

        {

        }

        public override void Eat()

        {

            Console.WriteLine("这是狗的吃饭方式");

        }

    }

            Dog newDog = new Dog();

            AnimalmyAnimal = newDog;

            myAnimal.Eat();

用子类的实例直接赋值给基类的实例,那么再用基类的实例就可以调用子类的方法和属性,为什么要这么做呢?主要是为了代码的精简。想一想,如果Dog类里面有很多方法和属性,重写了基类的方法,这个时候我们发现我们写错了,应该把Dog类改成Bird类,这个时候就要改动大量的代码,但是如果按照上述的语句,一行就够了,下面都可以不变,这就是上述方法存在的意义。

本站仅提供存储服务,所有内容均由用户发布,如发现有害或侵权内容,请点击举报
打开APP,阅读全文并永久保存 查看更多类似文章
猜你喜欢
类似文章
【热】打开小程序,算一算2024你的财运
虚方法
简述c#之sealed 修饰符
关于在C#中对类中的隐藏基类方法和重写方法的理解
C#虚方法和抽象方法
浅析继承关系中的方法调用
V4.2虚方法
更多类似文章 >>
生活服务
热点新闻
分享 收藏 导长图 关注 下载文章
绑定账号成功
后续可登录账号畅享VIP特权!
如果VIP功能使用有故障,
可点击这里联系客服!

联系客服