面向对象编程概念(OOP)等简介

目录

1.介绍

2.背景

3.先决条件

4.主要内容

4.1. 什么是软件架构?

4.2. 为什么架构很重要?​编辑

4.3. 什么是面向对象?

4.4. 什么是对象?

4.5. 什么是类?

​编辑

4.6. 如何识别和设计一个类?

4.7. 什么是封装(或信息隐藏)?

4.8. 什么是关联?

4.9. 关联、聚合和组合之间有什么区别?

4.10. 什么是抽象和泛化?

4.11. 什么是抽象类?

4.12. 什么是接口?

4.13. 类和接口有什么区别?

4.14. 接口和抽象类有什么区别?

4.15. 什么是隐式和显式接口实现?

4.16. 什么是继承?

4.17. 什么是多态性?

4.18. 什么是方法重载?

4.19. 什么是运算符重载?

4.20. 什么是方法覆盖?

4.21. 什么是用例?

4.22. 什么是类图?

4.23. 什么是包图?

4.24. 什么是序列图?

4.25. 什么是两层架构?

4.26. 什么是三层架构?

4.27. 什么是MVC架构?

4.28. 什么是SOA?

4.29. 什么是数据访问层?

4.30. 什么是业务逻辑层?

4.31. 什么是四人组(GoF)设计模式?

4.32. 抽象工厂和构建器设计模式有什么区别?

5.结论是什么?

接下来是什么?

6.我提到了什么?


1.介绍

我注意到在过去几个月中,在Code Project的架构类别中发表的文章数量有所增加。大多数这些文章的读者数量也很高,尽管文章的评分不高。这表明读者有兴趣阅读有关架构的文章,但质量与他们的期望不符。本文是对希望成为系统架构师的经验丰富的开发人员进行分组/定义/解释软件架构的所有介绍性概念的建设性尝试。

有一天,我读到一篇文章,说最富有的2%拥有世界一半的财富。它还表示,2000年,最富有的1%成年人拥有全球40%的资产。此外,最富有的10%成年人占世界总财富的85%。因此,物质世界中的财富分配不平衡。你有没有想过软件世界中知识的不平衡分布?根据我的观点,软件行业的大规模扩张正迫使开发人员使用已经实现的库、服务和框架在更短的时间内开发软件。新开发人员被训练使用(我会说更多)已经开发的软件组件来更快地完成开发。他们只是插入现有的库并以某种方式设法满足要求。但故事的可悲之处在于,他们从未接受过定义、设计架构和实现此类组件的培训。随着数年的流逝,这些开发人员成为领导者和软件架构师。他们的头衔发生了变化,但不理解、没有任何架构经验的旧遗产仍在继续,造成了优秀架构师的真空。底线是只有一小部分开发人员知道如何设计一个真正的面向对象系统。由于软件行业的激进性质不支持对现有流程进行轻松调整,并且相关的在线教材要么复杂,要么不太实用,甚至有时甚至是错误的,因此解决这个问题的难度越来越大。他们中的大多数使用形状、动物和许多其他物理世界实体的不切实际、不相关的示例来教授软件架构的概念。很少有好的面向业务的设计参考。不幸的是,我自己也不例外,也是这个系统的结果。我接受了与你们所有人相同的教育,并且还提到了你们都阅读的相同资源集。

回到最初的观点,我注意到在知道如何正确构建系统的架构师和不知道如何正确构建系统的架构师之间存在着每天都在增加的知识差距。知道的人,都知道是对的。但不知道的人,一无所知。就像世界的财富分配一样,知识的分配不平衡。

2.背景

本文开始于阅读并听取了新开发人员对软件架构基础提出的问题。那里有一些不错的文章,但开发人员仍然难以理解基本概念,更重要的是,正确应用它们的方法。

在我看来,新手总是很难理解新概念的精确定义,因为它总是一个新的,因此是陌生的想法。有经验的人理解其含义,但不努力理解定义的人。就是这样。雇主需要有经验的员工。所以他们说,你需要有经验才能找到工作。但是,如果没有人愿意给他一份工作,他怎么会有经验呢?与一般情况一样,从软件架构开始也不例外。这会很难。当你开始设计你的第一个系统时,你会尝试应用你所知道或从任何地方学到的一切。你会觉得需要为每个类定义一个接口,就像我曾经做过的那样。你会发现更难理解什么时候该做什么时候不做某事。只是准备经历痛苦的​​过程。别人会批评你,可能会嘲笑你,说你设计的方式是错误的。倾听他们,并不断学习。在这个过程中,你还需要大量阅读和思考。我希望这篇文章能给你这个漫长的旅程一个正确的开始。

3.先决条件

本文旨在为新开发人员提供有关软件架构基础知识的准确信息库,重点关注面向对象的编程(OOP)。如果您是一名开发人员,并且拥有至少三年的持续开发经验,并且渴望了解更多信息,想要进入下一个级别以成为一名软件架构师,那么本文适合您。

4.主要内容

4.1. 什么是软件架构?

软件架构被定义为以下规则、启发式和模式:

  • 将问题和系统划分为离散的部分
  • 用于在这些部分之间创建接口的技术
  • 用于管理整体结构和流程的技术
  • 用于将系统与其环境接口的技术
  • 适当使用开发和交付方法、技术和工具

4.2. 为什么架构很重要?

软件架构的主要目标是定义系统的非功能性需求并定义环境。详细设计之后是如何在架构规则内交付功能行为的定义。架构很重要,因为它:

  • 控制复杂性
  • 执行最佳实践
  • 提供一致性和统一性
  • 提高可预测性
  • 允许重复使用

4.3. 什么是面向对象?

OOP是一种设计哲学。它代表面向对象编程。面向对象的编程 (OOP)使用组不同于旧的过程编程语言(C Pascal等)的编程语言。OOP中的所有内容都被归类为自我可持续的对象。因此,您可以通过四个主要的面向对象编程概念获得可重用性。

为了清楚地理解面向对象模型,我们以你的为例。是一类。你的身体有两个类型为的物体,名为左手右手。它们的主要功能由一组通过您的肩膀(通过接口)发送的电信号控制或管理。所以肩膀是你的身体用来与你的手互动的接口。手是一个结构良好的类。通过稍微改变它的属性,手被重用以创建左手和右手。

4.4. 什么是对象?

一个对象可以被认为是一个可以执行一组相关活动的事物对象执行的活动集定义了对象的行为。例如,手(对象)可以抓住某物,或者学生(对象)可以给出他们的姓名或地址。

在纯OOP术语中,对象是类的实例。

4.5. 什么是类?

类只是一种对象类型的表示。它是描述对象细节的蓝图、计划或模板。类是创建各个对象的蓝图。由三部分组成:名称、属性和操作。

public class Student
{
}

根据下面给出的示例,我们可以说已在Student类之外创建了名为objectStudentStudent对象。

Student objectStudent = new Student();

在现实世界中,您经常会发现许多同类对象。例如,可能存在数千辆其他自行车,它们的品牌和型号都相同。每辆自行车都是根据相同的蓝图制造的。在面向对象的术语中,我们说自行车是称为自行车的对象类的一个实例。

在软件世界中,虽然您可能没有意识到,但您已经使用了类。例如,您经常使用的TextBox控件是由定义其外观和功能的TextBox类组成的。每次拖动TextBox控件时,实际上都是在创建该TextBox类的新实例。

4.6. 如何识别和设计一个类?

这是一门艺术;每个设计师使用不同的技术来识别类。但是,根据面向对象的设计原则,在设计类时必须遵循五个原则:

  1. SRP——单一职责原则——一个类应该有一个并且只有一个改变的理由。
  2. OCP——开放封闭原则——应该能够扩展任何类的行为,而无需修改类
  3. LSP——里式替换原则——派生类必须可以替换它们的基类。
  4. DIP——依赖倒置原则——依赖于抽象,而不是具体化
  5. ISP——接口隔离原则——制作特定于客户端的细粒度接口

有关设计原则的更多信息,请参阅Object Mentor

此外,为了正确识别一个类,您需要识别系统的叶级功能或操作的完整列表(系统的粒度级用例)。然后您可以继续将每个函数分组以形成类(类将分组相同类型的函数或操作)。然而,定义良好的类必须是一组功能的有意义的分组,并且应该支持整个系统的可重用性,同时增加可扩展性或可维护性。

在软件世界中,总是推荐分而治之的概念,如果你一开始就开始分析一个完整的系统,你会发现它更难管理。所以更好的方法是先识别系统的模块,然后分别深入每个模块,找出类。

一个软件系统可能由许多类组成。当你有很多类时,它需要被管理。想象一个大型组织,其员工人数超过数千人(我们以一名员工为一类)。为了管理这样的劳动力,您需要制定适当的管理政策。可以应用相同的技术来管理软件系统的类。为了管理软件系统的类并降低复杂性,系统设计人员使用了几种技术,这些技术可以归为以下四个主要概念:

  1. 封装
  2. 抽象
  3. 继承
  4. 多态性

这些概念是OOP世界的四大神,在软件术语中,它们被称为四个主要的面向对象编程(OOP)概念。

4.7. 什么是封装(或信息隐藏)?

封装是在程序对象中包含对象运行所需的所有资源,基本上是方法和数据。在OOP中,封装主要是通过创建类来实现的,类暴露public方法和属性。类是一种容器或胶囊或单元,它封装了一组方法、属性和属性,以向其他类提供其缩进功能。从这个意义上说,封装还允许类更改其内部实现,而不会损害系统的整体功能。封装的想法是隐藏一个类如何做它的业务,同时允许其他类对它提出请求。

为了模块化/定义一个类的功能,该类可以以许多不同的方式使用另一个类公开的函数或属性。根据面向对象编程,有几种技术类可以用来相互链接。这些技术被命名为关联、聚合和组合。

还有其他几种使用封装的方式,例如,我们可以使用接口。该接口可用于隐藏已实现类的信息。

IStudent myLStudent = new LocalStudent();
IStudent myFStudent = new ForeignStudent();

根据上面的示例(假设LocalStudentForeignStudent类都实现了IStudent接口) ,我们可以看到LocalStudentForeignStudent如何通过IStudent接口隐藏它们的本地化实现。

作为两个实例中的示例,'myLStudent''myFStudent'都是IStudent类型,但它们都在下面携带两个单独的本地和外部实现。这样,像'DoLearn(object)'这样对'myLStudent''myFStudent'对象的方法调用将触发它们各自的外部和本地实现。这样,'myFStudent'携带'ForeignStudent'将触发相应的外国课程学习功能,而另一个带有'LocalStudent'的将触发本地课程的学习功能。

4.8. 什么是关联?

关联是两个类之间的 (*a*) 关系。它允许一个对象实例使另一个对象实例代表它执行操作。关联是定义两个类之间关系的更笼统的术语,而聚合和组合则比较特殊。

public class StudentRegistrar
{
    public StudentRegistrar ();
    {
        new RecordManager().Initialize();
    }
}

在这种情况下,我们可以说StudentRegistrarRecordManager之间存在关联,或者存在从StudentRegistrarRecordManager的定向关联,或者StudentRegistrar使用(*Use*) RecordManager。由于明确指定了方向,因此在这个示例中,控制器类是StudentRegistrar

对于一些初学者来说,关联是一个令人困惑的概念。带来的麻烦不仅是关联本身,还有另外两个OOP概念,即关联、聚合和组合。在描述聚合和组合之前,每个人都了解关联。聚合或组合不能单独理解。单独理解聚合,会破解关联的定义,单独理解组合,总是威胁聚合的定义,这三个概念密切相关,因此必须放在一起研究,通过比较一个定义到另一个。让我们探索所有这三个,看看我们是否能理解这些有用概念之间的差异。

4.9. 关联、聚合和组合之间有什么区别?

关联*has-a*两个类之间没有特定所有权的关系。这只是两个类之间的连接。当您在另一个类中定义一个类的变量时,您首先启用了关联第二个类的函数和属性。再说一次,聚合组合都是关联的类型。

聚合是具有部分所有权的关联类型。对于聚合关系,我们使用该术语*uses*来暗示*has-a*关系。与组合相比,这很弱。再一次,弱意味着聚合器的链接组件可以在不存在其父对象的情况下在聚合生命周期中存活。例如,一个学校部门*uses*的老师。任何一位教师可能属于多个系。因此,如果一个部门不复存在,老师仍然存在。 

另一方面,组合是一种具有完全所有权的关联类型。与弱聚合相比,这很强大。对于组合关系,我们使用该术语*owns* 来暗示*has-a*关系。例如,一个部门*owns*的课程,这意味着任何课程的生命周期都取决于部门的生命周期。因此,如果一个部门不复存在,基础课程也将不复存在。

每当没有所有权时,我们将这种关系视为只是一个关联,我们只是使用该*has-a*术语,或者有时是描述这种关系的动词。例如,老师*has-a**teaches*学生。老师和学生之间没有所有权,每个人都有自己的生命周期。

public class University
{
    private Chancellor  universityChancellor = new Chancellor();
}

在上面给出的示例中,我可以说University聚合ChancellorUniversity具有(*has-a*) Chancellor。但即使没有ChancellorUniversity也存在。但是院系的存在离不开University, Faculty (或院系)的生命周期与University的生命周期相连。如果University被释放了,学院将不存在。在那种情况下,我们称之为UniversityFaculties组成。这样组合就可以被识别为一种特殊类型(强类型)的聚合。

同样,作为另一个示例,您可以说,在KeyValuePairCollectionKeyValuePair之间存在组合关系。就像学院和大学一样,两者相互依赖。

.NETJava使用组合关系来定义它们的集合。我看到组合也被用于许多其他方面。然而,大多数人忘记的更重要的因素是生命周期因素。具有组合关系的两个类的生命周期相互依赖。如果您使用.NET Collection来理解这一点,那么您在Collection内部定义了Collection元素(它是一个内部部分,因此称为它是由Collection组成的),从而使ElementCollection一起处理。如果不是,例如,如果您将Collection及其Element定义为独立的,则该关系将更像是聚合类型,而不是组合。所以关键是,如果你想用组合关系绑定两个类,更准确的方法是在另一个类中定义一个(使其成为受保护或私有类)。这样,您允许外部类实现其目的,同时将内部类的生命周期与外部类联系起来。

所以总而言之,我们可以说聚合是一种特殊的关联,组合是一种特殊的聚合。(关联->聚合->组合

4.10. 什么是抽象和泛化?

抽象是对思想、品质和属性的强调,而不是细节(对细节的抑制)。抽象的重要性源于其隐藏无关细节的能力以及使用名称来引用对象的能力。抽象在程序的构建中是必不可少的。它强调一个对象是什么或做什么,而不是它是如何表示的或它是如何工作的。因此,它是管理大型程序复杂性的主要手段。

抽象通过隐藏不相关的细节来降低复杂性,而泛化通过用单个构造替换执行相似功能的多个实体来降低复杂性。泛化是应用的扩展,以涵盖相同或不同类型的对象的更大领域。编程语言通过变量、参数化、泛型和多态提供泛化。它强调对象之间的相似性。因此,它通过将个人聚集成组并提供可用于指定组中任何个人的代表来帮助管理复杂性。

抽象和泛化经常一起使用。摘要通过参数化进行泛化以提供更大的效用。在参数化中,实体的一个或多个部分被替换为实体的新名称。名称用作参数。当参数化的抽象被调用时,它是通过将参数绑定到参数来调用的。

4.11. 什么是抽象类?

abstract关键字声明的抽象类不能被实例化。它只能用作扩展该abstract类的其他类的超类。Abstract类是概念,实现在由子类实现时完成。除此之外,一个类只能从一个abstract类继承(但一个类可以实现许多接口)并且必须覆盖其所有声明为abstract并且可能覆盖虚拟方法/属性的方法/属性。

Abstract类是实现框架时的理想选择。例如,让我们研究下面命名为LoggerBaseabstract类。请仔细阅读注释,因为它将帮助您理解此代码背后的原因:

public abstract class LoggerBase
{
    /// <summary>
    /// field is private, so it intend to use inside the class only
    /// </summary>
    private log4net.ILog logger = null;

    /// <summary>
    /// protected, so it only visible for inherited class
    /// </summary>
    protected LoggerBase()
    {
        // The private object is created inside the constructor
        logger = log4net.LogManager.GetLogger(this.LogPrefix);
        // The additional initialization is done immediately after
        log4net.Config.DOMConfigurator.Configure();
    }

    /// <summary>
    /// When you define the property as abstract,
    /// it forces the inherited class to override the LogPrefix
    /// So, with the help of this technique the log can be made,
    /// inside the abstract class itself, irrespective of its origin.
    /// If you study carefully, you will find a reason for not to have "set" method here.
    /// </summary>
    protected abstract System.Type LogPrefix
    {
        get;
    }

    /// <summary>
    /// Simple log method,
    /// which is only visible for inherited classes
    /// </summary>
    /// <param name="message"></param>
    protected void LogError(string message)
    {
        if (this.logger.IsErrorEnabled)
        {
            this.logger.Error(message);
        }
    }

    /// <summary>
    /// Public properties which expose to inherited class
    /// and all other classes that have access to inherited class
    /// </summary>
    public bool IsThisLogError
    {
        get
        {
            return this.logger.IsErrorEnabled;
        }
    }
}

拥有这个类作为abstract的想法是为异常日志定义一个框架。该类将允许所有子类访问一个通用的异常日志模块,并有助于轻松替换日志库。当您定义LoggerBase时,您不会对系统的其他模块有所了解。但是您确实有一个概念,那就是,如果一个类要记录异常,它们必须继承LoggerBase。换句话说,LoggerBase为异常日志记录提供了一个框架。

让我们试着理解上面代码的每一行。

像任何其他类一样,一个abstract类可以包含字段,因此我使用了一个名为loggerprivate字段声明著名的log4net库的ILog接口。这将允许Loggerbase类来控制,使用什么,进行日志记录,因此,将允许轻松更改logger库。

LoggerBase的构造函数的访问修饰符是protected。当类是abstract类型时,public构造函数没有用处。abstract不允许类实例化该类。所以我选择了protected构造函数。

命名为LogPrefixabstract属性是一个重要的属性。它强制并保证在每个子类调用方法记录错误之前,为LogPrefix(LogPrefix用于获取发生异常的源类的详细信息) 具有一个值。

命名为LogError的方法是受保护的,因此暴露给所有子类。你是不允许的,或者更确切地说,你不能像任何类一样使其是public,如果不继承LoggerBase就不能有意义地使用它。

让我们找出为什么名为IsThisLogError的属性是public。对于继承类的其他关联类,了解关联成员是否记录其错误可能很重要/有用。

除此之外,您还可以在abstract类中定义virtual方法。该virtual方法可能有其默认实现,子类可以在需要时覆盖它。

总而言之,这里的重要因素是,所有OOP概念都应谨慎使用,并说明理由,您应该能够从逻辑上解释为什么要创建属性为public或字段为private或类为abstract。此外,在构建框架时,OOP概念可用于强制引导系统按照框架架构师希望的最初构建方式进行开发。

4.12. 什么是接口?

总而言之,接口将实现分离并定义了结构,这个概念在您需要实现可互换的情况下非常有用。除此之外,当实现频繁更改时,接口非常有用。有人说你应该根据接口定义所有类,但我认为推荐似乎有点极端。

接口可用于定义通用模板,然后使用一个或多个abstract类来定义接口的部分实现。接口只指定方法声明(隐式publicabstract)并且可以包含属性(也隐式地publicabstract)。接口定义以关键字interface开头。abstract类的接口不能被实例化。

如果实现接口的类没有定义接口的所有方法,则必须声明它为abstract,并且方法定义必须由扩展该abstract类的子类提供。除此之外,一个接口可以继承其他接口。

下面的示例将为我们的LoggerBase abstract类提供一个接口。

public interface ILogger
{
    bool IsThisLogError { get; }
}

4.13. 类和接口有什么区别?

.NET/C#中,可以定义一个类来实现一个接口,并且它还支持多种实现。当一个实现一个接口时,该类的对象可以封装在一个接口内

如果MyLogger是一个类,它实现了ILogger,我们可以写:

ILogger log = new MyLogger();

类和接口是两种不同的类型(概念上)理论上,强调封装的思想,而接口强调抽象的思想(通过抑制实现的细节)。两者之间形成了明显的分离。因此,在两者之间进行有效的有意义的比较是非常困难的,甚至是不可能的,但是在接口和抽象类之间进行比较是非常有用和有意义的。

4.14. 接口和抽象类有什么区别?

接口抽象类之间有很大的区别,尽管两者看起来很相似:

  • 接口定义以关键字interface开头,因此它是interface类型。
  • 抽象类是用abstract关键字声明的,因此它是类类型。
  • 接口没有实现,但它们必须被实现。
  • 抽象类的方法可以有自己的默认实现,并且可以扩展。Abstract类的方法可以独立于继承类运行。
  • 接口只能有方法声明(隐式publicabstract)和属性(隐式public static
  • 抽象类的方法只有在声明abstract时才能实现。
  • 接口可以继承多个接口。
  • 抽象类可以实现多个接口,但只能继承一个类。
  • 抽象类必须覆盖所有抽象方法,并且可以覆盖虚拟方法。
  • 当实现发生变化时可以使用接口。
  • 抽象类可用于为基类提供一些默认行为。
  • 接口使实现可互换。
  • 接口通过隐藏实现来提高安全性。
  • 实现框架时可以使用抽象类。
  • 抽象类是创建计划继承层次结构以及在类层次结构中用作非叶子类的极好方法。

抽象类让你定义一些行为;他们强迫您的子类提供其他行为。例如,如果您有一个应用程序框架,则可以使用一个抽象类来提供服务的默认实现以及所有必需的模块,例如事件记录和消息处理等。这种方法允许开发人员在引导的范围内开发应用程序框架提供的帮助。

但是,在实践中,当您遇到一些只有您的应用程序才能执行的特定于应用程序的功能时,例如启动和关闭任务等。抽象基类可以声明虚拟关闭和启动方法。基类知道它需要这些方法,但是抽象类让你的类承认它不知道如何执行这些操作;它只知道它必须启动这些动作。当需要启动时,抽象类可以调用启动方法。基类调用该方法时,可以执行子类定义的方法。

4.15. 什么是隐式和显式接口实现?

如前所述,.NET支持多种实现,隐式和显式实现的概念通过隐藏、公开或保留每个接口方法的标识,即使方法签名相同,也为实现多个接口的方法提供了安全的方法。

让我们考虑下面定义的接口:

interface IDisposable
{
    void Dispose();
}

在这里,您可以看到该 Student类已隐式和显式地通过DisposeIDisposable.Dispose实现了名为Dispose()的方法。

class Student : IDisposable
{
    public void Dispose()
    {
        Console.WriteLine("Student.Dispose");
    }

    void IDisposable.Dispose()
    {
        Console.WriteLine("IDisposable.Dispose");
    }
}

4.16. 什么是继承?

通过扩展现有类来创建新类的能力称为继承

public class Exception
{
}

public class IOException : Exception
{
}

根据上面的例子,新类(IOException),称为派生类或子类,继承了现有类(Exception)的成员,该类称为基类或超类。IOException类可以通过添加新的类型和方法以及覆盖现有的来扩展Exception类的功能。

正如抽象与泛化密切相关,继承与专业化密切相关。将这两个概念与概括一起讨论以更好地理解和降低复杂性非常重要。

现实世界中对象之间最重要的关系之一是专业化,可以描述为is-a关系。当我们说狗是哺乳动物时,我们的意思是狗是一种特殊的哺乳动物。它具有任何哺乳动物的所有特征(幼崽、哺乳、有毛发),但它把这些特征专门用于家犬的熟悉特征。猫也是哺乳动物。因此,我们希望它与哺乳动物中普遍存在的狗具有某些共同特征,但在猫中专门化的那些特征上有所不同。

专业化和泛化关系既是相互的又是层次的。专业化只是泛化硬币的另一面:哺乳动物概括了狗和猫之间的共同点,而狗和猫则将哺乳动物专门化为它们自己的特定亚型。

同样,作为示例,您可以说IOExceptionSecurityException两者都是Exception类型。它们具有异常的所有特征和行为,这意味着IOException是一种特殊的异常。SecurityException也是一个Exception。因此,我们希望它与Exception中泛化的IOException共享某些特征,但在SecurityException中专门化的那些特征上有所不同。换句话说,Exception泛化了IOExceptionSecurityException的共同特征,而IOExceptionSecurityException专门化了它们的特征和行为。

OOP中,特化关系是使用称为继承的原理来实现的。这是实现这种关系的最常见、最自然和被广泛接受的方式。

4.17. 什么是多态性?

多态性是一个通用术语,意思是许多形状。更准确地说,多态性意味着能够请求由各种不同类型的事物执行相同的操作。

有时,我曾经认为理解面向对象编程概念变得很困难,因为它们分为四个主要概念,而每个概念彼此密切相关。因此,必须非常小心地分别正确理解每个概念,同时理解每个概念与其他概念的关联方式。

OOP中,多态性是通过使用许多不同的技术来实现的,称为方法重载、运算符重载和方法覆盖,

4.18. 什么是方法重载?

方法重载是定义多个同名方法的能力。

public class MyLogger
{
    public void LogError(Exception e)
    {
        // Implementation goes here
    }

    public bool LogError(Exception e, string message)
    {
        // Implementation goes here
    }
}

4.19. 什么是运算符重载?

运算符重载(不太常见的是ad-hoc 多态性)是多态性的一种特殊情况,其中一些或所有运算符(如+-==)被视为多态函数,因此根据其类型具有不同的行为论据。

public class Complex
{
    private int real;
    public int Real
    { get { return real; } }

    private int imaginary;
    public int Imaginary
    { get { return imaginary; } }

    public Complex(int real, int imaginary)
    {
        this.real = real;
        this.imaginary = imaginary;
    }

    public static Complex operator +(Complex c1, Complex c2)
    {
        return new Complex(c1.Real + c2.Real, c1.Imaginary + c2.Imaginary);
    }
}

在上面的例子中,我重载了加号运算符来添加两个复数。在那里,名为RealImaginary的两个属性已被声明为仅公开所需的get方法,而对象的构造函数则要求使用类的用户定义构造函数来强制实数和虚数值。

4.20. 什么是方法覆盖?

方法覆盖是一种语言功能,它允许子类覆盖其超类之一已经提供的方法的特定实现。

子类可以给出自己的方法定义,但需要与其超类中的方法具有相同的签名。这意味着当重写一个方法时,子类的方法必须与超类的重写方法具有相同的名称和参数列表。

using System;
public class Complex
{
    private int real;
    public int Real
    { get { return real; } }

    private int imaginary;
    public int Imaginary
    { get { return imaginary; } }

    public Complex(int real, int imaginary)
    {
        this.real = real;
        this.imaginary = imaginary;
    }

    public static Complex operator +(Complex c1, Complex c2)
    {
        return new Complex(c1.Real + c2.Real, c1.Imaginary + c2.Imaginary);
    }

    public override string ToString()
    {
        return (String.Format("{0} + {1}i", real, imaginary));
    }
}

在上面的例子中,我扩展了在运算符重载部分给出的示例Complex类的实现。此类有一个名为ToString的重写方法,它重写标准ToString方法的默认实现以支持复数的正确string转换。

Complex num1 = new Complex(5, 7);
Complex num2 = new Complex(3, 8);

// Add two Complex numbers using the
// overloaded plus operator
Complex sum = num1 + num2;

// Print the numbers and the sum
// using the overridden ToString method
Console.WriteLine("({0}) + ({1}) = {2}", num1, num2, sum);
Console.ReadLine();

4.21. 什么是用例?

用例是参与者从系统中感知到的东西。用例将参与者映射到函数。重要的是,参与者不必是人。例如,当一个系统与另一个系统通信时,它可以扮演参与者的角色。

从另一个角度来看,用例对典型的用户与系统的交互进行编码。特别是,它:

  • 捕获一些用户可见的功能。
  • 为用户实现了一些具体的目标。

一套完整的用例在很大程度上定义了您的系统的需求:用户可以看到和想做的所有事情。下图包含一组用例,描述了游戏网站的简单登录模块。

4.22. 什么是类图?

类图广泛用于描述系统中对象的类型及其关系。类图使用类、包和对象等设计元素对类结构和内容进行建模。类图描述了设计系统、概念、规范和实现时的三个不同视角。随着图表的创建和帮助巩固设计,这些观点变得明显。

在我看来,类图、物理数据模型以及系统概览图是适合当今快速应用程序开发需求的最重要的图。

UML符号:

4.23. 什么是包图?

包图用于反映包及其元素的组织。当用于表示类元素时,包图​​提供了名称空间的可视化。在我的设计中,我使用包图将类组织到系统的不同模块中。

4.24. 什么是序列图?

序列图以可视方式对系统内的逻辑流进行建模,它可以记录和验证您的逻辑,并用于分析和设计目的。序列图是最流行的用于动态建模的UML工件,它侧重于识别系统内的行为。

4.25. 什么是两层架构?

两层架构也指客户端/服务器架构,客户端/服务器一词在1980年代首次用于指代网络上的个人计算机(PC)。实际的客户端/服务器模型在1980年代后期开始获得认可,后来它适应了万维网编程。

根据现代使用的两层架构,用户界面(或使用ASP.NET,所有网页)在客户端上运行,而数据库存储在服务器上。实际的应用程序逻辑可以在客户端或服务器上运行。所以在这种情况下,用户界面是直接访问数据库的。这些也可以是非接口处理引擎,为其他远程/本地系统提供解决方案。无论哪种情况,今天的两层模型都不如三层模型那样有名。两层设计的优势在于其简单性,但简单性伴随着可扩展性的成本。较新的三层架构更为著名,它为应用程序逻辑引入了中间层。

4.26. 什么是三层架构?

三层软件架构(也称为三层架构)出现在1990年代,以克服两层架构的局限性。这种架构已被现代系统设计人员积极定制并采用到Web系统。

三层是一种客户端-服务器架构,其中用户界面、功能处理逻辑、数据存储和数据访问作为独立的模块开发和维护,有时在不同的平台上。术语三层三层以及多层体系结构(通常称为三层体系结构)的概念似乎起源于Rational Software

三层架构有以下三层:

  1. 表示层或Web服务器:用户界面,显示/接受数据//来自用户的输入
  2. 应用程序逻辑/业务逻辑/事务层或应用程序服务器:数据验证、添加到数据库之前的可接受性检查以及所有其他业务/应用程序特定操作
  3. 数据层或数据库服务器:对数据库或任何其他存储、连接、命令、存储过程等的简单读写方法。

4.27. 什么是MVC架构?

模型-视图-控制器(MVC)架构将域的建模、表示和基于用户输入的操作分为三个单独的类。

不幸的是,这种模式的流行导致了许多错误的用法。每种技术(JavaASP.NET等)都以自己的方式对其进行了定义,因此难以理解。特别是,控制器一词在不同的上下文中被用来表示不同的事物。下面给出的定义是我为ASP.NET版本的MVC找到的最接近的定义。

  1. 模型:数据和类型化数据(有时是业务对象、对象集合、XML等)是模型最常见的用途。
  2. 视图ASPXASCX文件通常处理视图的职责。
  3. 控制器:事件的处理或控制通常在代码隐藏类中完成。

在复杂的n层分布式系统中,MVC架构扮演了组织系统表示层的重要角色。

4.28. 什么是SOA

面向服务的架构本质上是服务的集合。这些服务相互通信。通信可以涉及简单的数据传递,也可以涉及协调某些活动的两个或多个服务。需要一些将服务相互连接的方法。

.NET Framework通过Web服务引入了SOA

SOA可以作为连接多个系统来提供服务的概念。它在IT世界的未来中占有很大的份额。

根据上面的假想图,我们可以看到面向服务的架构是如何被用来为一个国家的公民提供一组中心化服务的。公民获得一张独特的身份证,该卡包含每个公民的所有个人信息。每个服务中心,如购物中心、医院、车站和工厂,都配备了一个计算机系统,该系统连接到一个中央服务器,该服务器负责为城市提供服务。例如,当顾客进入购物中心时,区域计算机系统将其报告给中央服务器,并在提供对场所的访问之前获取有关顾客的信息。系统欢迎客户。顾客完成购物,然后在他离开购物中心时,他将被要求完成一个计费流程,由区域计算机系统管理该流程。付款将使用从客户识别卡中获取的输入详细信息自动处理。

区域系统上报城市(城市计算机系统),城市上报国家(国家计算机系统)。

4.29. 什么是数据访问层?

数据访问层(DAL)是每个n层系统的关键部分,主要由一组简单的代码组成,这些代码与数据库或任何其他存储设备进行基本交互。这些功能通常称为CRUD(创建、检索、更新和删除)。

数据访问层需要尽可能通用、简单、快速和高效。它不应包含复杂的应用程序/业务逻辑。

我见过具有冗长而复杂的存储过程(SP)的系统,它们在进行简单检索之前会运行多个案例。它们不仅包含大部分业务逻辑,还包含应用程序逻辑和用户界面逻辑。如果SP变得更长和更复杂,那么这很好地表明您正在将业务逻辑埋入数据访问层。

4.30. 什么是业务逻辑层?

我知道这是大多数人的问题,但另一方面,通过阅读许多文章,我意识到并不是每个人都同意实际的业务逻辑,而且在许多情况下,它只是演示之间的桥梁层和数据访问层什么都没有,除了取自一个并传递到另一个。在其他一些情况下,甚至没有经过深思熟虑,他们只是从表示层和数据访问层中取出剩余部分,然后将它们放在另一个自动称为业务逻辑层的层中。但是,在软件世界中,没有上帝说的不能改变的事情。您可以随时更改,因为您应用的方法足够灵活以支持系统的增长。有很多很棒的方法,但是在选择它们时要小心,它们会使简单的系统过于复杂。这是一个人需要在自己的经验中找到的平衡点。

作为一般建议,当您定义业务实体时,您必须决定如何将表中的数据映射到正确定义的业务实体。考虑到各种类型的需求和系统的功能,业务实体应该有意义地定义。建议识别业务实体以封装应用程序的功能/UI(用户界面)需求,而不是为数据库的每个表定义单独的业务实体。例如,如果您想组合来自几个表的数据来构建一个UI(用户界面)控件(Web控件),请在业务逻辑层中使用一个业务对象来实现该功能,该业务对象使用几个数据对象来支持您的复杂业务需求。

4.31. 什么是四人组(GoF)设计模式?

四人组(GoF)模式通常被认为是所有其他模式的基础。它们分为三组:创造型、结构型和行为型。在这里,您将找到有关这些重要模式的信息。

创作模式

  • 抽象工厂——创建几个类族的实例
  • 建造者——将对象构造与其表示分离
  • 工厂方法——创建几个派生类的实例
  • 原型——要复制或克隆的完全初始化的实例
  • 单例——只能存在一个实例的类

结构模式

  • 适配器——匹配不同类的接口
  • 桥接——将对象的接口与其实现分开
  • 组合——简单和复合对象的树结构
  • 装饰器——动态地为对象添加职责
  • 外观——代表整个子系统的单个类
  • 享元——用于高效共享的细粒度实例
  • 代理——代表另一个对象的对象

行为模式

  • 职责链——在对象链之间传递请求的一种方式
  • 命令——将命令请求封装为对象
  • 解释器——一种在程序中包含语言元素的方法
  • 迭代器——顺序访问集合的元素
  • 中介者——定义类之间的简化通信
  • 备忘录——捕获并恢复对象的内部状态
  • 观察者——一种通知多个类更改的方法
  • 状态——当状态改变时改变对象的行为
  • 策略——将算法封装在一个类中
  • 模板方法——将算法的确切步骤推迟到子类
  • 访问者——为一个类定义一个新的操作而不做任何改变

4.32. 抽象工厂和构建器设计模式有什么区别?

这两种设计模式根本不同。但是,当您第一次学习它们时,您会看到令人困惑的相似之处。所以这会让你更难理解它们。但是如果你最终继续学习,你也会害怕设计模式。就像婴儿恐惧症一样,一旦你从小害怕,它就会永远伴随着你。所以结果是你再也不会回头看设计模式了。让我看看我能否为你解决这个脑筋急转弯。

在下图中,您列出了两种设计模式。我试图一对一地比较两者以识别相似之处。如果你仔细观察这个图,你会看到一个易于理解的颜色模式(相同的颜色用于标记同类)。

阅读以下列表时,请按照图片中的数字进行跟进:

  • 标记#1:两种模式都使用泛型类作为入口类。唯一的区别是类的名称。一种模式将其命名为Client,而另一种模式将其命名为Director
  • 标记#2:这里的区别还是类名。一个是AbstractFactory,另一个是Builder。此外,这两个类都是abstract类型。
  • 标记#3:再一次,两种模式都定义了两个通用(WindowsFactoryConcreteBuilder)类。他们都是通过继承各自的abstract类来创建的。
  • Mark #4:最后,两者似乎都产生了某种通用输出。

现在,我们在哪里?他们看起来不是几乎一模一样吗?那么,为什么我们在这里有两种不同的模式呢?

让我们最后一次将两者并排比较,但这一次,重点是不同之处。

  • 抽象工厂:强调一系列产品对象(简单或复杂)
  • 建造者:专注于逐步构建复杂的对象
  • 抽象工厂:专注于*what*是做什么的
  • 建造者:专注于*how*它是制造的
  • 抽象工厂:专注于定义许多不同类型的*工厂*来构建许多*产品*,它不是一个产品的建造者
  • 建造者:专注于构建一个复杂但单一的*产品*
  • 抽象工厂:推迟选择要制作的具体对象类型,直到运行时
  • 建造者:隐藏如何编译该复杂对象的逻辑/操作
  • 抽象工厂*every*方法调用创建并返回不同的对象
  • 建造者: 只有*last*方法调用返回对象,而其他调用部分构建对象

有时创建模式是互补的:因此您可以在设计系统时加入一种或多种模式。作为示例,构建器可以使用其他模式之一来实现构建哪些组件,或者在另一种情况下,抽象工厂、构建器和原型可以在其实现中使用单例。因此得出的结论是,这两种设计模式的存在是为了解决两种类型的业务问题,因此即使它们看起来相似,但实际上并非如此。

我希望这有助于解决这个难题。如果你还是不明白,那这次不是你,是我,我不知道该怎么解释。

5.结论是什么?

我不认为试图让编程语言成为所有人的一切是不现实的。如果把所有东西加上厨房水槽都扔进去,语言就会变得臃肿、难以学习和阅读。换句话说,每种语言都有其局限性。作为系统架构师和设计师,我们应该能够充分且更重要的是正确地(这也意味着您不应该使用弹道导弹杀死苍蝇或雇用FBI来捕捉苍蝇)利用可用的工具和功能来构建可用的、可持续、可维护且非常重要的可扩展软件系统,充分利用语言的特性为客户带来具有竞争力的先进系统。为了做到这一点,系统的基础起着至关重要的作用。软件系统的设计或架构基础。它将系统结合在一起,因此正确设计系统(这绝不意味着*过度*设计)是成功的关键。当你谈到设计一个软件系统时,正确处理OOP概念是非常重要的。我已经使上述文章的想法更加丰富,但仍然保持简短,以便人们可以一目了然地学习/提醒所有重要概念。希望大家会喜欢阅读它。

接下来是什么?

学习所有这些理论很好,但是如果你不知道如何将它们付诸实践,可以问学习它们的意义。这些需要给我们一些好处,对吧?因此,我认为我应该至少在一个具体项目中演示这些概念的使用。

因此,我们的一个团队开发了一个名为“Nido”框架的框架。这实际上演示了大多数这些OOP概念的使用。

如今,它在开发人员中非常流行。Nido Framework是使用Microsoft .NET/C#开发的可重用、高度通用的代码库,为所有.NET系统提供通用平台。您可以从Nido派生您的项目并快速开发您的系统。

如果你想用你的编码速度打败你的同事,或者想满足那些你总是未能按时完成的艰难的最后期限,请尝试使用Nido。今天,大多数大学生和专业人士使用Nido进行发展并分享他们的成功故事。

我们已经用许多复杂的业务应用程序对其进行了测试,证明它可以在极短的时间内提供稳定、强大的系统。如果您想查看Nido源代码或了解架构,请访问下面给出的链接。顺便说一句,它是开源的,可以在商业项目中免费使用。

6.我提到了什么?

https://www.codeproject.com/Articles/22769/Introduction-to-Object-Oriented-Programming-Concep

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值