从设计基类及其派生类看继承关系

继承能够定义可重用、扩展或修改父类行为的子类。但基类的静态构造函数、实例构造函数和析构函数不能被派生类继承。

在下面实例中,定义一个基类Publication用于表示任何类型的出版物以及派生至Publication的其他类型Book类,由此也可以扩展为定义其他类型如:Magazine、Journal、Newspaper和Article。

在设计基类Publication时我们必须考虑到如下关系:

1.要在基类中添加哪些成员

2.基类是否用作派生类模板的抽象基类

3.类层次结构的扩展空间大小,要开发包含三个或多个的类的层次结构,如Publication是Periodical的基类,又可以是Magazine和Journal的基类

4.能否重写基类实现的代码,如果容许重写,必须在基类中使用关键字virtual,派生类才能允许重写基类方法。

5.派生类是否必须继承结构的终结类,且本身不被用作其他派生类的基类,这时可以用sealed 关键字来标识该类

先定义一个出版物类型的枚举

 public enum PublicationType:long
    {
        [Description("报纸")]
        Newspaper=1,
        [Description("杂志")]
        Magazine =2,
        [Description("书籍")]
        Book=3
    }

再定义Publication抽象基类

public abstract class Publication
    {
        private bool published = false;
        private DateTime datePublished;
        private int totalPages;
        public Publication(string title,string publisher,PublicationType type)
        {
            if (string.IsNullOrWhiteSpace(title))
                throw new ArgumentNullException("title can not be null or white space");
            Title = title;
            if (string.IsNullOrWhiteSpace(publisher))
                throw new ArgumentNullException("publisher can not be null or white space");
            Publisher = publisher;
            Type = type;
        }
        public string Publisher { get; }
        public string Title { get; }
        public PublicationType Type { get; }
        public string CopyrightName { get; private set; }
        public int CopyrightDate { get; private set; }
        public int Pages
        {
            get { return totalPages; }
            set
            {
                if (value < 0) throw new ArgumentOutOfRangeException("The number of pages cannot be zero or negative");
                totalPages = value;
            }
        }
        public string GetPublicationDate()
        {
            if (!published) return "NYP";
            else return datePublished.ToString("d");
        }

        public void Publish(DateTime datePublished)
        {
            published = true;
            this.datePublished = datePublished;
        }

        /// <summary>
        /// 定义所有权名称和期限
        /// </summary>
        /// <param name="copyrightName"></param>
        /// <param name="copyrightDate"></param>
        ///<remarks></remarks>
        public void Copyright(string copyrightName,int copyrightDate)
        {
            if (string.IsNullOrEmpty(copyrightName))
                throw new ArgumentNullException("The copyright name can not be null or empty");
            CopyrightName = copyrightName;
            var currentYear = DateTime.Now.Year;
            if (copyrightDate < currentYear - 10 || copyrightDate > currentYear + 2)
                throw new ArgumentOutOfRangeException($"the copyright year must be validate");
            CopyrightDate = copyrightDate;
        }
        public override string ToString() => Title;
    }

Book表示一种类型的出版物,继承至Publication

public sealed class Book:Publication
    {
        public Book(string title,string guid,string author, string publisher) : base(title, publisher, PublicationType.Book)
        {
            if (string.IsNullOrEmpty(guid))
                throw new ArgumentNullException("GUID can not be null or empty");
            GUID = guid;
            Author = author;
        }

        public string GUID { get; }
        public string Author { get; }
        public decimal Price { get; private set; }
        public string Currency { get; private set; }

        /// <summary>
        /// 设置新的价格返回旧价格
        /// </summary>
        /// <param name="price"></param>
        /// <param name="currency"></param>
        /// <remarks></remarks>
        /// <returns>旧价格</returns>
        public decimal SetPrice(decimal price, string currency)
        {
            if (price < 0)
                throw new ArgumentOutOfRangeException("price can not be negative");
            var oldPrice = Price;
            Price = price;
            if (currency.Length != 3)
                throw new ArgumentNullException("the currency is a 3-character string");
            Currency = currency;
            return oldPrice;
        }
        public override bool Equals(object obj)
        {
            var book = obj as Book;
            return book == null ? false : GUID.Equals(book.GUID);
        }

        public override int GetHashCode() => GUID.GetHashCode();
        public override string ToString() => $"{(string.IsNullOrEmpty(Author) ? "" : Author + ",")}{Title}";
    }

在代码图中查看Book和Publication类的依赖关系和类中成员的引用关系

使用反射分别获取Book和Publication类的成员列表

 class Program
    {
        static void Main(string[] args)
        {
            var tPublication = typeof(Publication);
            var tBook = typeof(Book);
            var flags = BindingFlags.Instance | BindingFlags.Static | BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.FlattenHierarchy;
            var membersPublication = tPublication.GetMembers(flags);
            var membersBook = tBook.GetMembers(flags);
            OutputClassInfo(tPublication, membersPublication);
            OutputClassInfo(tBook, membersBook);
            Console.ReadKey();
        }

        private static void OutputClassInfo(Type t, MemberInfo[] members)
        {
            Console.WriteLine($"Type {t.Name} has {members.Length} members:");
            foreach (var member in members)
            {
                var access = string.Empty;
                var stat = string.Empty;
                if (member is MethodBase method)
                {
                    if (method.IsPublic)
                        access = "Public";
                    else if (method.IsPrivate)
                        access = "Private";
                    else if (method.IsFamily)
                        access = "Protected";
                    else if (method.IsAssembly)
                        access = "Internal";
                    else if (method.IsFamilyOrAssembly)
                        access = "Protected Internal";
                    if (method.IsStatic)
                        stat = "Static";
                }
                var output = $"{member.Name} ({member.MemberType}): {access}{stat}, Declared by {member.DeclaringType}";
                Console.WriteLine(output);
            }
        }

从输出结果分析可得出:

1、Publication 和Book都隐式继承自基类

2、派生类只能有一个一个直接基类,当然可以隐式继承object

3、继承是可以传递的。

参考文档:https://docs.microsoft.com/zh-cn/dotnet/articles/csharp/tutorials/inheritance

时间: 2024-08-09 06:42:54

从设计基类及其派生类看继承关系的相关文章

C++ Primer 学习笔记_66_面向对象编程 --定义基类和派生类[续]

算法旨在用尽可能简单的思路解决问题,理解算法也应该是一个越看越简单的过程,当你看到算法里的一串概念,或者一大坨代码,第一感觉是复杂,此时不妨从例子入手,通过一个简单的例子,并编程实现,这个过程其实就可以理解清楚算法里的最重要的思想,之后扩展,对算法的引理或者更复杂的情况,对算法进行改进.最后,再考虑时间和空间复杂度的问题. 了解这个算法是源于在Network Alignment问题中,图论算法用得比较多,而对于alignment,特别是pairwise alignment, 又经常遇到maxim

C++ Primer 学习笔记_65_面向对象编程 --概述、定义基类和派生类

面向对象编程 --概述.定义基类和派生类 引言: 面向对象编程基于的三个基本概念:数据抽象.继承和动态绑定. 在C++中,用类进行数据抽象,用类派生从一个类继承另一个:派生类继承基类的成员.动态绑定使编译器能够在运行时决定是使用基类中定义的函数还是派生类中定义的函数. 继承和动态绑定在两个方面简化了我们的程序:[继承]能够容易地定义与其他类相似但又不相同的新类,[派生]能够更容易地编写忽略这些相似类型之间区别的程序. 面向对象编程:概述 面向对象编程的关键思想是多态性(polymorphism)

C++:基类和派生类

4.1 派生类的声明 继承实例如下: class Person{ //声明基类Person public: void print() { cout<<"name:"<<name<<endl; cout<<"age:"<<age<<endl; cout<<"sex:"<<sex<<endl; } protected: string name;

c++中基类与派生类中隐含的this指针的分析

先不要看结果,看一下你是否真正了解了this指针? 1 #include<iostream> 2 using namespace std; 3 4 class Parent{ 5 public: 6 int x; 7 Parent *p; 8 public: 9 Parent(){} 10 Parent(int x){ 11 this->x=x; 12 p=this; 13 } 14 virtual void f(){ 15 cout<<"Parent::f()&q

C++学习21 基类和派生类的赋值

在C/C++中,经常会发生数据类型转换,例如整型数据可以赋值给浮点型变量,在赋值之前,先把整型数据转换为浮点型:反过来,浮点型数据也可以赋值给整型变量. 数据类型转换的前提是,编译器知道如何对数据进行取舍.例如: int a = 10.9; printf("%d\n", a); 输出结果为 10,编译器会将小数部分直接丢掉(不是四舍五入).再如: float b = 10; printf("%f\n", b); 输出结果为 10.000000,编译器会自动添加小数部

C++ Primer 学习笔记_65_面向对象编程 -概述、定义基类跟派生类

面向对象编程 --概述.定义基类和派生类 引言: 面向对象编程基于的三个基本概念:数据抽象.继承和动态绑定. 在C++中,用类进行数据抽象,用类派生从一个类继承另一个:派生类继承基类的成员.动态绑定使编译器能够在运行时决定是使用基类中定义的函数还是派生类中定义的函数. 继承和动态绑定在两个方面简化了我们的程序:[继承]能够容易地定义与其他类相似但又不相同的新类,[派生]能够更容易地编写忽略这些相似类型之间区别的程序. 面向对象编程:概述 面向对象编程的关键思想是多态性(polymorphism)

C++ Primer 学习笔记_66_面向对象编程 -定义基类跟派生类[续]

面向对象编程 --定义基类和派生类[续] 四.virtual与其他成员函数 C++中的函数调用默认不使用动态绑定.要触发动态绑定,必须满足两个条件: 1)只有指定为虚函数的成员函数才能进行动态绑定,成员函数默认为非虚函数,非虚函数不进行动态绑定. 2)必须通过基类类型的引用或指针进行函数调用. 1.从派生类到基类的转换 因为每个派生类对象都包含基类部分,所以可以将基类类型的引用绑定到派生类对象的基类部分可以用指向基类的指针指向派生类对象: void print_total(const Item_

详解C++中基类与派生类的转换以及虚基类

很详细!转载链接 C++基类与派生类的转换在公用继承.私有继承和保护继承中,只有公用继承能较好地保留基类的特征,它保留了除构造函数和析构函数以外的基类所有成员,基类的公用或保护成员的访问权限在派生类中全部都按原样保留下来了,在派生类外可以调用基类的公用成员函数访问基类的私有成员.因此,公用派生类具有基类的全部功能,所有基类能够实现的功能, 公用派生类都能实现.而非公用派生类(私有或保护派生类)不能实现基类的全部功能(例如在派生类外不能调用基类的公用成员函数访问基类的私有成员).因此,只有公用派生

C++:基类与派生类对象之间的赋值兼容关系

4.5 基类与派生类对象之间的赋值兼容关系 在一定条件下,不同类型的数据之间可以进行类型转换,例如可以将整型数据赋给双精度型变量. 在赋值之前,先把整型数据转换为双精度型数据,然后再把它双精度型变量.这种不同类型之间的自动转换,称为赋值兼容.在基类和派生类对象之间也存在有赋值兼容关系,基类和派生类对象之间的赋值兼容规则是指在需要基类对象的任何地方,都可以使用公有派生类的对象来代替.因为,通过公有继承,除了构造函数和析构函数外,派生类保留了基类其他的所有的成员.那么,派生类就具有基类的全部功能,凡

基类与派生类的构造函数

一.缺省构造函数的调用关系 通过下面的例子,我们来看一下基类与派生的构造函数的调用顺序.创建时先基类后派生类.销毁时先派生类后基类. #include <iostream> #include <string> using namespace std; class CBase { string name; int age; public: CBase() { cout <<"BASE" << endl; } ~CBase() { cout