• OOAD面向对象分析与设计


    面向对象包括面向对象的分析(Object-Oriented Analysis)、面向对象的设计(Object-Oriented Design)、面向对象的编程(Object-Oriented Programming)等。

             面向对象分析OOA):是一种分析方法,它以在问题域的词汇表中找到的类和对象的观点来审视需求。

             面向对象设计OOD):是一种设计方法,它包含面向对象的分解过程,以及一种表示方法,用来描写设计中的系统的逻辑模型与物理模型,以及静态模型与动态模型。

             面向对象编程OOP):是一种实现方法,程序被组织成对象的协作集合,每个对象代表某个类的实例,对象的类是通过继承关系联合在一起的类层次中的所有成员。

    面向对象分析方法(Object-Oriented AnalysisOOA

    面向对象分析方法(Object-Oriented AnalysisOOA),是在一个系统的开发过程中进行了系统业务调查以后,按照面向对象的思想来分析问题。OOA与结构化分析有较大的区别OOA所强调的是在系统调查资料的基础上,针对OO方法所需要的素材进行的归类分析和整理,而不是对管理业务现状和方法的分析。

          OOA(面向对象的分析)模型由5个层次(主题层、对象类层、结构层、属性层和服务层)和5个活动(标识对象类、标识结构、定义主题、定义属性和定义服务)组成。在这种方法中定义了两种对象类之间的结构,一种称为分类结构,一种称为组装结构。分类结构就是所谓的一般与特殊的关系。组装结构则反映了对象之间的整体与部分的关系。

          OOA在定义属性的同时,要识别实例连接。实例连接是一个实例与另一个实例的映射关系。

          OOA在定义服务的同时要识别消息连接。当一个对象需要向另一对象发送消息时,它们之间就存在消息连接。

          OOA 中的5个层次和5个活动继续贯穿在OOD(画向对象的设计)过程中。OOD模型由4个部分组成。它们分别是设计问题域部分、设计人机交互部分、设计任务管理部分和设计数据管理部分。

    一、OOA的主要原则

          1)抽象:从许多事物中舍弃个别的、非本质的特征,抽取共同的、本质性的特征,就叫作抽象。抽象是形成概念的必须手段。

          抽象原则有两方面的意义:第一,尽管问题域中的事物是很复杂的,但是分析员并不需要了解和描述它们的一切,只需要分析研究其中与系统目标有关的事物及其本质性特征。第二,通过舍弃个体事物在细节上的差异,抽取其共同特征而得到一批事物的抽象概念。

          抽象是面向对象方法中使用最为广泛的原则。抽象原则包括过程抽象和数据抽象两个方面。

          过程抽象是指,任何一个完成确定功能的操作序列,其使用者都可以把它看作一个单一的实体,尽管实际上它可能是由一系列更低级的操作完成的。

          数据抽象是根据施加于数据之上的操作来定义数据类型,并限定数据的值只能由这些操作来修改和观察。数据抽象是OOA的核心原则。它强调把数据(属性)和操作(服务)结合为一个不可分的系统单位(即对象),对象的外部只需要知道它做什么,而不必知道它如何做。

           2)封装就是把对象的属性和服务结合为一个不可分的系统单位,并尽可能隐蔽对象的内部细节。

           3)继承:特殊类的对象拥有的其一般类的全部属性与服务,称作特殊类对一般类的继承。

          OOA中运用继承原则,就是在每个由一般类和特殊类形成的一般特殊结构中,把一般类的对象实例和所有特殊类的对象实例都共同具有的属性和服务,一次性地在一般类中进行显式的定义。在特殊类中不再重复地定义一般类中已定义的东西,但是在语义上,特殊类却自动地、隐含地拥有它的一般类(以及所有更上层的一般类)中定义的全部属性和服务。继承原则的好处是:使系统模型比较简练也比较清晰。

           4)分类:就是把具有相同属性和服务的对象划分为一类,用类作为这些对象的抽象描述。分类原则实际上是抽象原则运用于对象描述时的一种表现形式。

           5)聚合:又称组装,其原则是:把一个复杂的事物看成若干比较简单的事物的组装体,从而简化对复杂事物的描述。

           6)关联:是人类思考问题时经常运用的思想方法:通过一个事物联想到另外的事物。能使人发生联想的原因是事物之间确实存在着某些联系。

           7)消息通信:这一原则要求对象之间只能通过消息进行通信,而不允许在对象之外直接地存取对象内部的属性。通过消息进行通信是由于封装原则而引起的。在OOA中要求用消息连接表示出对象之间的动态联系。

          8)粒度控制:一般来讲,人在面对一个复杂的问题域时,不可能在同一时刻既能纵观全局,又能洞察秋毫。因此需要控制自己的视野:考虑全局时,注意其大的组成部分,暂时不详察每一部分的具体的细节;考虑某部分的细节时则暂时撇开其余的部分。这就是粒度控制原则。

          9)行为分析:现实世界中事物的行为是复杂的。由大量的事物所构成的问题域中各种行为往往相互依赖、相互交织。
     
    二、面向对象分析产生三种分析模型

          1、功能模型(即用例模型à作为输入)

          2、对象模型:对用例模型进行分析,把系统分解成互相协作的分析类,通过类图/对象图描述对象/对象的属性/对象间的关系,是系统的静态模型

          3、动态模型:描述系统的动态行为,通过时序图/协作图描述对象的交互,以揭示对象间如何协作来完成每个具体的用例,单个对象的状态变化/动态行为可以通过状态图来表达

    三、OOA的主要优点

         1)加强了对问题域和系统责任的理解;
        
    2)改进与分析有关的各类人员之间的交流;
        
    3)对需求的变化具有较强的适应性;
        
    4)支持软件复用。
        
    5)贯穿软件生命周期全过程的一致性。
        
    6)实用性;
        
    7)有利于用户参与。
     
    四、OOA方法的基本步骤
          
    在用OOA具体地分析一个事物时,大致上遵循如下五个基本步骤:
          
    第一步,确定对象和类。这里所说的对象是对数据及其处理方式的抽象,它反映了系统保存和处理现实世界中某些事物的信息的能力。类是多个对象的共同属性和方法集合的描述,它包括如何在一个类中建立一个新对象的描述。
         
    第二步,确定结构(structure)。结构是指问题域的复杂性和连接关系。类成员结构反映了泛化-特化关系,整体-部分结构反映整体和局部之间的关系。
          
    第三步,确定主题(subject)。主题是指事物的总体概貌和总体分析模型。     

           第四步,确定属性(attribute)。属性就是数据元素,可用来描述对象或分类结构的实例,可在图中给出,并在对象的存储中指定。
          
    第五步,确定方法(method)。方法是在收到消息后必须进行的一些处理方法:方法要在图中定义,并在对象的存储中指定。对于每个对象和结构来说,那些用来增加、修改、删除和选择一个方法本身都是隐含的(虽然它们是要在对象的存储中定义的,但并不在图上给出),而有些则是显示的。

    面向对象设计(OOD)思想(C#

    有了思想才能飞翔,缺乏灵活就象少了轮子的汽车,难以飞奔。为了更好的理解设计思想,结合一个尽可能简洁的实例来说明OOD、设计模式及重构。通过下面的代码,详细地阐述面向对象设计思想。 

    一、传统过程化设计思想

    假定我们要设计一个媒体播放器(只从软件设计的角度,不涉及硬件)。该媒体播放器目前只支持音频文件mp3wav。按照结构化设计思想,设计出来的播放器的代码如下:

    public class MediaPlayer 

    {   

       private void PlayMp3() 

       { 

          MessageBox.Show("Play the mp3 file."); 

       } 

     

       private void PlayWav() 

       { 

          MessageBox.Show("Play the wav file."); 

       } 

     

       public void Play(string audioType) 

       {      

          switch (audioType.ToLower()) 

          { 

              case ("mp3"): 

                 PlayMp3(); 

                 break; 

              case ("wav"): 

                 PlayWav(); 

                 break;             

          }      

       } 

    从传统的过程化设计思想来看,这是一段既实用又简洁的代码。

    如果,客户又提出新的要求:要播放器不仅仅播放mp3wav文件,还要播放其他音频文件如wmamp4等,为此我们要不断地增加相应地播放方法和修改条件语句,直止条件语句足够长。

    如果,客户感到这个媒体播放器功能太少了,只能闻其声,不能见其人,太单一。如果在听着优美音乐的同时又能看到歌唱者潇洒、英俊的舞姿那就更好了。从代码设计的角度看,他们希望媒体播放器支持视频文件了。也许你会想,不会再增加视频这方面的代码,可以,在增加视频媒体的播放方法,在修改条件判断语句,如果还有其他,还可以同样地增加、修改。到此你也许会提出,要是不修改或很少修改原来的代码就能增添其他功能该多好啊!

    这样看,原来的软件设计结构似乎有点问题。事实上,随着功能的不断增加,你越来越发现这个设计非常的糟糕,因为它根本没有为未来的需求变更提供最起码的扩展。为了应接不暇的变更需求,你不得不不厌其烦地修改原来的代码,使其适应需求变化,甚至在修改代码时,由于过多的代码依赖关系弄得人焦头烂额,直止一塌糊涂。

    二、面向对象设计思想

    还是以设计一个媒体播放器为例,设计要求相同。不访我们换个设计思路利用面向对象设计思想(OOD来做做看如何!

    根据OOD的思想,我们应该把mp3wav分别看作是两个独立的对象。代码设计如下:

    public class MP3 

       public void Play() 

       { 

           MessageBox.Show("Play the mp3 file."); 

       } 

     

    public class WAV 

       public void Play() 

       { 

           MessageBox.Show("Play the wav file."); 

       } 

     

    Public class MediaPlayer

    {

          switch (audioType.ToLower()) 

          { 

              case ("mp3"): 

                          MP3 m = new MP3();

                 m.Play(); 

                 break; 

              case ("wav"): 

                 WAV w = new WAV();

    w.Play(); 

                 break;             

          }

    }     

           现在我们重构代码,建立统一的Play()方法,(在后面的设计中,你会发现这样改名是多么的重要!)更改媒体播放类MediaPlayer的代码。如果这样的设计代码,实质上没有多大的变化,只是对原来过程化设计思想的一种替代,并没有击中要害,亦然没有灵活性、可扩展性。

           2.1单向分派技术的应用(在这里用类的多态来实现的)

    我们不访这样设想:既然mp3wav都属于音频文件,都具有音频文件的共性,应该建立一个共同的AudioMedia父类。 

    public class AudioMedia 

       public void Play() 

       { 

           MessageBox.Show("Play the AudioMedia file."); 

       } 

    现在引入继承思想,OOD就有点雏形了(不是说有了继承就有了OOD思想,这里只是从继承的角度谈一谈OOD思想,当然从其他角度如合成、聚合等角度也能很好地体现OOD思想)。

    其实在现实生活中,我们的播放器播放的只能是某种具体类型的音频文件如mp3,因此这个AudioMedia类只能是音频媒体的一个抽象化概念,并没有实际的使用情况。对应在OOD设计中,既这个类永远不会被实例化。为此我们应将其改为抽象类,如下:

    public abstract class AudioMedia 

       public abstract void Play(); 

     

    public class MP3:AudioMedia 

       public override void Play() 

       { 

           MessageBox.Show("Play the mp3 file."); 

       } 

     

    public class WAV:AudioMedia 

       public override void Play() 

       { 

           MessageBox.Show("Play the wav file."); 

       } 

     

    public class MediaPlayer 

    {  

           //根据需要完成任务的单向分派

       public void Play(AudioMedia media) 

       {      

           media.Play(); 

       } 

    到此,我们通过单向分派技术使OOD思想得到进一步的体现。现在的设计,即满足了类之间的层次关系,又保证了类的最小化原则,同时又体现了面向对象设计原则(开闭原则、里氏代换原则)更利于扩展。(止此,你会发现play方法名的更改是多么必要)。 

    如果现在又增加了对WMAMP4等音频文件的播放,只需要设计WMA类,MP4类,并继承AudioMedia,在相应的子类中重写Play方法就可以了,MediaPlayer类对象的Play方法根本不用任何改变。 

    如果让媒体播放器能够支持视频文件,必须另外设计视频媒体的类。因视频文件和音频文件有很多不同的地方,不可能让视频继承音频。假设我们播放器支持RMMPEG格式的视频。视频类代码如下: 

    public abstract class VideoMedia 

       public abstract void Play(); 

     

    public class RM:VideoMedia 

       public override void Play() 

       { 

           MessageBox.Show("Play the rm file."); 

       } 

     

    public class MPEG:VideoMedia 

       public override void Play() 

       { 

           MessageBox.Show("Play the mpeg file."); 

       } 

     

    这样设计还是有点糟糕,这样就无法实用原有的MediaPlayer类了。因为你要播放的视频RM文件并不是音频媒体AudioMedia的子类。

           不过,我们可以这样想,无论音频媒体还是视频媒体都是媒体,有很多相似的功能,如播放、暂停、停止等,为此我们把媒体这个概念抽象出来做为一个接口。(虽然也可以用抽象类,但在C#里只支持类的单继承,不过c#支持接口的多继承)。根据接口的定义,你完全可以将相同功能的一系列对象实现同一个接口。让音频媒体类及视频媒体类都继承媒体这个接口。代码如下: 

     

    public interface IMedia 

       void Play(); 

     

    public abstract class AudioMedia:IMedia 

       public abstract void Play(); 

     

    public abstract class VideoMedia:IMedia 

       public abstract void Play(); 

     

    这样再更改MediaPlayer类的代码: 

    public class MediaPlayer 

    {  

       public void Play(IMedia media) 

       {      

           media.Play(); 

       }  

           现在看来,程序是不是有很大的灵活性和可扩展性了。

    总结一下,从MediaPlayer类的演变,我们可以得出这样一个结论:在调用类对象的属性和方法时,尽量避免将具体类对象作为传递参数,而应传递其抽象对象,更好地是传递接口,将实际的调用和具体对象完全剥离开,这样可以很好地体现了软件工程的灵活性、扩展性。 

           现在看起来似乎很完美了,但我们忽略了MediaPlayer的调用者这个事实。仍然需要条件语句来实现。例如,在客户端程序代码中,用户通过选择cbbMediaType组合框的选项,决定播放音频媒体还是视频媒体,然后单击Play按钮执行。 

    Public void BtnPlay_Click(object sender,EventArgs e) 

        IMedia media = null;

        switch (cbbMediaType.SelectItem.ToString().ToLower()) 

        { 

            case ("mp3"): 

                 media = new MP3(); 

                 break; 

                          //其它类型略;

            case ("rm"): 

                 media = new RM(); 

                 break;   

            //其它类型略; 

        } 

        MediaPlayer player = new MediaPlayer(); 

        player.Play(media); 

     

    2.2设计模式、条件外置及反射技术的应用

    随着需求的增加,程序将会越来越复杂。此时就应调整设计思想,充分考虑到代码的重构和设计模式的应用。最后当设计渐趋完美后,你会发现,即使需求不断增加,你也可以神清气爽,不用为代码设计而烦恼了。 

    为了实现软件工程的三个主要目标:重用性、灵活性和扩展性。我们不访用设计模式、条件外置及反射来实现。

    使用工厂模式,能够很好地根据需要,调用不同的对象(即动态调用),保证了代码的灵活性。

    虽然这里有两种不同类型的媒体AudioMediaVideoMedia(以后可能更多),但它们同时又都实现IMedia接口,所以我们可以将其视为一种产品。媒体工厂接口如下: 

    public interface IMediaFactory 

       IMedia CreateMedia(); 

     

    然后为具体的媒体文件对象搭建工厂,并统一实现媒体工厂接口: 

    public class MP3Factory:IMediaFactory 

       public IMedia CreateMedia() 

       { 

           return new MP3(); 

       } 

    //其它工厂略;

     

    public class RMFactory:IMediaFactory 

       public IMedia CreateMedia() 

       { 

           return new RM(); 

       } 

    //其它工厂略; 

     

    写到这里,也许有人会问,为什么不直接给AudioMediaVideoMedia类搭建工厂呢?很简单,因为在AudioMediaVideoMedia中,分别还有不同的类型派生,如果为它们搭建工厂,则在CreateMedia()方法中,仍然要使用条件判断语句,代码缺乏灵活性,不利扩展。

    还有一个问题,就是真的有必要实现AudioMediaVideoMedia两个抽象类吗?让其子类直接实现接口不是更简单?对于本文提到的需求,是能实现的。但不排除AudioMediaVideoMedia它们还会存在其他区别,如音频文件还需给声卡提供接口,而视频文件还需给显卡提供接口。如果让MP3WAVRMMPEG直接实现IMedia接口,而不通过AudioMediaVideoMedia,在满足其它需求的设计上也是不合理的。现在客户端程序代码发生了稍许的改变: 

    Public void BtnPlay_Click(object sender,EventArgs e) 

    IMediaFactory factory = null; 

        switch (cbbMediaType.SelectItem.ToString().ToLower()) 

           //音频媒体

            case ("mp3"): 

                 factory = new MP3Factory(); 

                 break; 

            //视频媒体

    case ("rm"): 

                 factory = new RMFactory(); 

                 break;   

            //其他类型略; 

        } 

        MediaPlayer player = new MediaPlayer(); 

        player.Play(factory.CreateMedia()); 

     

    到这里,我们再回过头来看MediaPlayer类。这个类中通过单向分派,根据传递参数的不同,分别实现了不同对象的Play方法。在不用工厂模式时,这个类对象会运行得很好。作为一个类库或组件设计者来看,他提供了一个不错的接口,供客户端程序调用。

    利用工厂模式后,现在看来MediaPlayer类已经多余。所以,我们要记住的是,重构并不仅仅是往原来的代码添加新的内容。当我们发现一些不必要的设计时,还需要果断地删掉这些冗余代码。修改后的代码如下: 

    Public void BtnPlay_Click(object sender,EventArgs e) 

    IMediaFactory factory = null; 

        switch (cbbMediaType.SelectItem.ToString().ToLower()) 

        {        

            case ("mp3"): 

                 factory = new MP3Factory(); 

                 break; 

                  //其他类型略;

            case ("rm"): 

                 factory = new RMFactory(); 

                 break;   

            //其他类型略; 

        } 

        IMedia media = factory.CreateMedia(); 

        media.Play(); 

     

    如果你在最开始没有体会到IMedia接口的好处,在这里你应该已经明白了。我们在工厂模式中用到了该接口;而在客户端程序中,仍然要使用该接口。使用接口有什么好处?那就是你的主程序可以在没有具体业务类的时候,同样可以编译通过。因此,即使你增加了新的业务,你的客户端程序是不用改动的。 

    不过,这样写客户端代码还是不够理想的,依然不够灵活,在判断具体创建哪个工厂的时候,仍需条件判断。现在看来,如果执行者没有完全和具体类分开,一旦更改了具体类的业务,例如增加了新的工厂类,仍然需要更改客户端程序代码。

    我们可以通过反射技术、条件外置很好地做到客户端的灵活性。

    条件外置来实现,即通过应用程序的配置文件来实现。我们可以把每种媒体文件类的类型信息放在配置文件中,然后根据配置文件来选择创建具体的对象。并且,这种创建对象的方法将使用反射技术来完成。首先,创建配置文件: 

     

    <appSettings> 

     <add key="mp3" value="MediaLibrary.MP3Factory" /> 

     <add key="wav" value=" MediaLibrary.WAVFactory" /> 

     <add key="rm" value=" MediaLibrary.RMFactory" /> 

     <add key="mpeg" value=" MediaLibrary.MPEGFactory" /> 

    </appSettings> 

     

    然后,在客户端程序代码中,自定义一个初始化方法如:InitMediaType(),读取配置文件的所有key值,填充cbbMediaType组合框控件中: 

    private void InitMediaType() 

    cbbMediaType.Items.Clear(); 

    foreach (string key in ConfigurationSettings.AppSettings.AllKeys) 

                cbbMediaType.Item.Add(key); 

    cbbMediaType.SelectedIndex = 0; 

     

    最后,更改客户端程序的Play按钮单击事件: 

    Public void BtnPlay_Click(object sender,EventArgs e) 

    string mediaType = cbbMediaType.SelectItem.ToString().ToLower(); 

    string factoryDllName = ConfigurationSettings.AppSettings[mediaType].ToString(); 

    //MediaLibray为引用的媒体文件及工厂的程序集

    IMediaFactory factory = (IMediaFactory)Activator.CreateInstance(“MediaLibrary”,

    factoryDllName).Unwrap();

    IMedia media = factory.CreateMedia(); 

    media.Play(); 

     

    这样可以很好地体现了软件工程的三个主要目标:重用性、灵活性和扩展性。

    设想一下,如果我们要增加某种媒体文件的播放功能,如AVI文件。那么,我们只需要在原来的业务程序集中创建AVI类,继承于VideoMedia类。另外在工厂业务中创建AVIFactory类,并实现IMediaFactory接口。假设这个新的工厂类型为MediaLiabrary.AVIFactory,则在配置文件中添加如下一行: 

    <add key="AVI" value="MediaLiabrary.AVIFactory" /> 

    而客户端程序呢?根本不需要做任何改变,甚至不用重新编译,程序就能自如地运行!

    另:本文发布后有热心的朋友提出,如果提供UML图,那就更完美了,谢谢这位朋友的提醒。UML图现补充如下:

  • 相关阅读:
    ....
    CodeForces 375A(同余)
    POJ 2377 Bad Cowtractors (最小生成树)
    POJ 1258 AgriNet (最小生成树)
    HDU 1016 Prime Ring Problem(全排列)
    HDU 4460 Friend Chains(bfs)
    POJ 2236 Wireless Network(并查集)
    POJ 2100 Graveyard Design(尺取)
    POJ 2110 Mountain Walking(二分/bfs)
    CodeForces 1059B Forgery(模拟)
  • 原文地址:https://www.cnblogs.com/majunfeng/p/3933867.html
Copyright © 2020-2023  润新知