C# - 面向对象 - 继承与组合


C# - 面向对象 - 继承与组合

松耦合设计思想

  • 高内聚、低耦合
  • 衡量一个软件代码的标准,可以判断软件设计的好坏

耦合

  • 耦合是一种测量各种类和子系统关系的方法。

如何实现低耦合

  • 封装( encapsulate ),对业务逻辑实现细节的隐藏。
  • 类的关联性

    • 依赖、关联、聚合、组合、泛化等等
  • 使用接口

    • 面向对象的思想核心,解决耦合的基本思路,重点中的重点

继承

  • 继承描述的是两个class之间的关系。
  • 通过继承可以允许一个类获得另一个类的所有代码。

继承的好处

  • 省去绝大部分重复性的代码、提高了代码的复用性。
  • 多态

    • 继承对象的某一个行为却具有多个不同表现形式的能力。

语法结构

实例

PresentationObject

using System;

namespace _20inherit
{
    class PresentationObject { 
        public int Width { get; set; }
        public int Height { get; set; }
        public void Copy() {
            Console.WriteLine("复制");
        }
        public void Paste()
        {
            Console.WriteLine("粘贴");
        }
    }
}

Program.cs

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;

namespace _20inherit
{
    class Text : PresentationObject { 
        public int FontSize { get; set; }
        public int FontName { get; set; }
        public void AddHyperLink() {
            Console.WriteLine("添加超链接");
        }
    }
    internal class Program
    {
        static void Main(string[] args)
        {
            var text = new Text();
            text.Width = 100;
            text.Copy();
            Console.Read();
        }
    }
}

复合

  • 描述的是两个class之间的关系。
  • 不同类之间的包含与被包含的关系。

好处

  • 解决代码复用的问题,复合的灵活度更高。
  • 完成对象之间的依赖注入,适合大型项目的开发。

实例

数据库迁移程序

  • 数据迁移工具 DbMigrator,负责处理具体数据库的迁移工作。
  • 日志系统logger,记录安装和数据迁移过程,共享组建。
  • 安装程序installer,负责向操作系统中安装我们程序。

Logger.cs

using System;

namespace _21Composition
{
    class Logger {
        public void Log(string message) { 
            Console.WriteLine($"日志:{DateTime.Now} - {message}");
        }
    }
}

DbMigrator.cs

namespace _21Composition
{
    class DbMigrator {
        private readonly Logger _logger;
        public DbMigrator(Logger logger)
        {
            _logger = logger;
        }

        public void Migrate() {
            _logger.Log("数据迁移开始");
            //TODO
        }
    }
}

Install.cs

namespace _21Composition
{
    class Install {
        private readonly Logger logger;

        public Install(Logger logger)
        {
            this.logger = logger;
        }
        public void install (){
            logger.Log("安装开始");
            //TODO
        }
    }
}

Program.cs

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;

namespace _21Composition
{
    internal class Program
    {
        static void Main(string[] args)
        {
            var logger = new Logger();
            var dbMigrate = new DbMigrator(logger);
            var install = new Install(logger);

            install.install();
            dbMigrate.Migrate();

            Console.Read();
        }
    }
}

访问修饰符(protected与internal)

访问修饰符回顾

protected

  • 只能被自己、或者被继承于自己的子类访问。

实例

Car.cs

using System;

namespace HelloWord
{
    public class Wuling : Car { 
        public void Drift() {
            this.Accelerate();
            this.Stop();
        }
    }
    public class Car
    {
        public void Accelerate()
        {
            Console.WriteLine("加油");
        }

        //protected
        protected void Stop()
        {
            Console.WriteLine("制动");
        }
    }
}

Program.cs

using System;

namespace HelloWord
{
    class Program
    {
        static void Main(string[] args)
        {
            var wulin = new Wuling();
            wulin.Accelerate();
            wulin.Drift();

            Console.Read();
        }
    }
}

internal

  • 访问范围限定在同一个项目的程序集中。

实例

创建类库

  • 剪切car.cs代码至类库中。

添加项目引用

internal修饰符

namespace CarLibrary
{
    internal class WulinHongguang : Car
    {
        public void Drift()
        {
            this.Accelerate();
            this.Stop();
        }
    }
}

using CarLibrary;

  • 如果将类库中的方法使用internal,则这此中不能使用。

构造函数的继承

构造方法继承的要点

  • 在初始化时,基类构造方法总是会首先运行。
  • 基类的构造方法不会被继承,在派生类中需要重新定义。

base关键词

  • 用来调用基类的有参数的构造函数,因为子类不能直接继承父类的构造函数。
  • base可以完成创建派生类实例时调用其基类构造函数或者调用基类上已被其他方法重写的方法。
  • base 关键字用于从派生类中访问基类的成员的构造函数的形参。
  • 调用基类上已被其他方法重写的方法。
  • 指定创建派生类实例时应调用的基类构造函数。

实例

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;

namespace _23Constructor_inheritance
{
    public class Staff { 
        public Staff() {
            Console.WriteLine("员工类");
        }

        public int Number { get; set; }
        public Staff(int number)
        {
            this.Number = number;
        }
    }

    public class Manager:Staff
    {
        public Manager()
        {
            Console.WriteLine("监理类");
        }
        

        //base关键词
        public Manager(int number):base(number)
        {
            Console.WriteLine($"{number}监理初始化");
        }
    }
    internal class Program
    {
        static void Main(string[] args)
        {
            var manager = new Manager(123);
            Console.WriteLine(manager.Number);
            Console.ReadLine();
        }
    }
}

向上转型与向下转型

  • 向上转型(upcasting):把一个派生类类型转换为他的基类。
  • 向下转型(downcasting):是把一个基类转换为他的某个派生类。

as关键词

is关键词

  • 使用is关键词,我们可以检查对象的类型。

实例

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;

namespace _24Upcasting_Downcasting
{
    internal class Program
    {
        public class Shape{
            public int X { get; set; }
            public int Y { get; set; }
            public int Width { get; set; }
            public int Height { get; set; }

            public void Draw() {
                Console.WriteLine($"{Width}+{Height}+{X}+{Y}");
            }
        }

        public class Text:Shape
        {
            public int FontSize { get; set; }
            public int FontName { get; set; }

        }

        static void Main(string[] args)
        {
            var text = new Text();

            //向上转型
            Shape shape = text;
            var shapeList = new List<Shape>(); 
            shapeList.Add(new Shape());
            shapeList.Add(text);

            //向下转型
            //显式转化
            Shape shape1= shapeList[1];
            var textList = new List<Text>();
            shapeList.ForEach(s =>
            {
                if (shape1 is Text)
                {
                    var shape_Text = (Text)shape1;
                }
            });
            
            
            Console.ReadLine();
        }
    }
}

装箱与拆箱

值类型 vs 引用类型

值类型

  • 值类型保存在 Stack(栈)内。
  • 基本数据类型(如byte、int、float、char、bool),以及结构(struct)。
  • 内存由编译器自动分配,程序结束内存会自动被回收。

引用类型

  • 引用类型保存在 heap(堆)内存中。
  • 数据长度不固定,数据类型、结构复杂。
  • 程序猿手动释放内存,但Java和C#有自动垃圾回收机制。
  • Class、对象属于引用类型,保存在heap中。

装箱boxing

  • 一个值类型转化一个引用类型的过程,我们就把它称为装箱boxing
  • 基本类型变为对象类型。
  • 从值类型变为引用类型。
  • 保存位置也会从stack转移到heap中。
  • 装箱就是把一个基本数据使用对象包装起来,就好像装箱子的过程。

拆箱unboxing

  • 从对象中打开,提取基本数据类型。

注意

  • 不管是装箱还是拆箱,都会有比较明显的性能问题。
  • 因为这些操作都涉及额外的对象的创建和销毁。

实例(使用“泛型列表”代替)

using System;
using System.Collections;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;

namespace _25boxing_Unboxing
{
    internal class Program
    {
        static void Main(string[] args)
        {
            var array = new ArrayList();
            array.Add(1); //装箱
            array.Add("ddddd");  //不装箱
            array.Add(DateTime.Today);//装箱
            //严重问题:
            //① 类型不安全
            var number = (int)array[1];

            //② 可能有装箱操作,性能有问题

            //使用“泛型列表”代替
            var list = new List<int>();
            list.Add(1);
            Console.Read();
        }
    }
}

声明:三二一的一的二|版权所有,违者必究|如未注明,均为原创|本网站采用BY-NC-SA协议进行授权

转载:转载请注明原文链接 - C# - 面向对象 - 继承与组合


三二一的一的二