




已阅读5页,还剩21页未读, 继续免费阅读
版权说明:本文档由用户提供并上传,收益归属内容提供方,若内容存在侵权,请进行举报或认领
文档简介
模式:模式,即Pattern。其实就是解决某一类问题的方法论。把解决某类问题的方法总结归纳到理论高度,那就是模式。Alexander给出的经典定义是:每个模式都描述了一个在我们的环境中不断出现的问题,然后描述了该问题的解决方案的核心。通过这种方式,你可以无数次地使用那些已有的解决方案,无需再重复相同的工作。模式有不同的领域,建筑领域有建筑模式,软件设计领域也有设计模式。当一个领域逐渐成熟的时候,自然会出现很多模式。设计模式和面向对象的设计模式:设计模式(Design pattern)是一套被反复使用、多数人知晓的、经过分类编目的、代码设计经验的总结。使用设计模式是为了可重用代码、让代码更容易被他人理解、保证代码可靠性。设计模式最初来源于建筑学。GOF(“四人帮”,指Gamma, Helm, Johnson & Vlissides, Addison-Wesley四人)的设计模式(1995年出版)是第一次将设计模式提升到理论高度,并将之规范化,本系列文章主要就是讲解这23种经典的设计模式。面向对象设计的模式,顾名思义,就是在面向对象分析与设计中使用的设计模式,GOF23种设计模式同时也是面向对象的设计模式,本文不做区分。良好的设计模式运用可以实现软件设计的“高内聚、低耦合”,提高软件的复用性和可扩展性。框架:框架,即Framework。其实就是某种应用的半成品,就是一组组件,供你选用完成你自己的系统。简单说就是使用别人搭好的舞台,你来做表演。而且,框架一般是成熟的,不断升级的软件。框架一般处在低层应用平台(如J2EE)和高层业务逻辑之间的中间层。架构:架构(Architecture)是一系列相关的抽象模式,用于指导大型软件系统各个方面的设计。架构是一个系统的草图。架构描述的对象是直接构成系统的抽象组件。各个组件之间的连接则明确和相对细致地描述组件之间的通讯。在实现阶段,这些抽象组件被细化为实际的组件,比如具体某个类或者对象。在面向对象领域中,组件之间的连接通常用接口来实现。一些刚入门的程序员经常会混淆“框架”和“架构”这两个名词,这里做了一下解释。我们为什么要使用设计模式呢?有人可能会说为了设计出高内聚低耦合的软件。高内聚低耦合的软件实际上也就是本文所说的具有可维护性和可复用性的软件。这篇文章主要讲解两方面内容,这两方面是软件设计中很重要,也是很关键的内容,希望大家认真思考并深刻理解。第一部分就是关于软件的可维护性和可复用性的相关内容,第二部分就是在第一部分的基础上逐条讲解面向对象软件设计的基本原则,本文内容都是些很理论性的东西,这些理论是软件设计的基础。凡是有理论的地方,就有如何恰当的将理论应用到实践中去的问题,设计模式是对于学习OO设计原则的具体指导,也就是说设计模式就是将这些理论应用到实践的一种成熟的方式。软件的可维护性和可复用性首先来上一段大师所说的话,很经典。“通常认为,一个易于维护的系统,就是复用率较高的系统;而一个复用率较高的系统,就是一个易于维护的系统。但是实际上,可维护性和可复用性是两个独立的目标,就像两只奔跑的兔子一样,并不总是方向一致的。对于面向对象的软件系统设计来说,在支持可维护性(Maintainability)的同时,提高系统的可复用性(Reuseability)是一个核心的问题。”Java与模式阎宏博士 软件系统的可维护性:软件维护就是软件的再生。一个好的软件设计,必须能够允许新的设计要求以比较容易和平稳的方式加入到已有的系统中去,从而使这个系统能够不断的的焕发出活力。一个可维护性较好的系统,应当允许维护工作能够以容易、准确、安全和经济的形式进行。【导致可维护性较低的原因】1、过于僵硬:在系统中加入一个新的功能,不管大小都很难,不仅意味着建造一个独立的新的模块,而且因为这个新功能会波及很多其他模块,最后成跨越几个模块的改动。2、过于脆弱:与软件的过于僵硬同时存在,是软件系统在修改已有代码时过于脆弱。对一个地方的修改,往往会导致看上去没有什么关系的另外一个地方发生故障。3、复用率低:所谓复用,就是指一个软件的组成部分,可以在同一个项目的不同地方甚至另一个项目中重复使用。复用率低,指当一段代码,函数,模块的功能可以在新的模块或新的系统使用,但是已有代码依赖于其他很多东西,很难分开。4、黏度过高:一个改动可以保存原始设计意图和原始设计框架的方式进行,也可以以破坏原始意图和框架进行。第一种方法对系统的未来有利,第二种办法是权宜之计,可以解决短期的问题,但是会牺牲中长期的利益。如果一个系统中使用第二种方法比使用第一种方法容易,那么就是黏度过高。【设计的目标】1、可扩展性:新的性能可以很容易地加入到系统中去,就是可扩展性。这就是系统“过于僵硬”的属性的方面。2、灵活性:可以允许代码修改平稳地发生,而不会波及到很多其他的模块,这就是灵活性。灵活性其实就是“过于脆弱”的属性的方面。3、可插入性:可以很容易地将一个类抽出去,同时将另外一个有同样接口的类加入进来,这就是可插入性。其实,这就是“黏度过高”的方面。 软件系统的可复用性:【软件复用的好处】1、较高的生产效率;2、较高的软件质量;3、恰当使用复用可以改善系统的可维护性。【传统的复用形式】1、代码的剪贴复用;2、算法的复用;3、数据结构的复用。【面向对象设计的复用】在面向对象语言中,数据的抽象化,继承,封装和多态性使得一个系统可以在更高层次上提供可复用性。数据的抽象化和继承关系使得概念和定义可以复用;多态性使得实现和应用得到复用;而抽象化和封装可以保持和促进系统的可维护性,复用的重点转移到含有宏观商业逻辑的抽象层次上。在面向对象的设计里面,可维护性复用是以设计原则和设计模式为基础的。提高系统可维护性和可复用性的设计原则1、“开闭”原则(Open-Closed Principle,或者OCP);一个软件实体应该对扩展开放,对修改关闭;在设计一个模块的时候,应当使这个模块可以在不被修改的前提下被扩展。换言之,应当可以在不必修改源代码的情况下改变这个模块的行为。这个原则实际上是对“对可变性的封闭原则“:找到一个系统的可变因素,将之封装起来。这个原则意昧着两点:1) 一个可变性不应当散落在代码的很多角落里,而应当被封装到一个对象里面。同一种可变性的不同表象意昧着同一个继承等级结构中的具体子类。继承就当被看作是封装变化的方法,而不应当被认为是从一般的对象生成特殊对象的方法。2) 一种可变性不应当与另一种可变性混合在一起。(所有类图的继承结构一般不会超过两层,不然就意昧着将两种不同的可变性混合在了一起。)这个原则是总的原则,其它几条是这个原则的手段和工具。2、里氏替代原则(Liskov Substitution Principle,或者LSP);如果对于每一个类型为T1的对象o1,都有类型为T2的对象o2,使得以T1定义的所有程序P在所有的对象o1都代换成o2时,程序P的行为没有变化,那么类型T2是类型T1的子类型。换言之,一个软件实体如果使用的是一个基类的话,那么一定适用于其子类,而且它根本不能察觉出基类对象和子类对象的区别。反过来代换不成立。3、依赖倒转原则(Dependency Inversion Principle,或者DIP);要依赖于抽象,不要依赖于具体。开闭原则是目标,而达到这一目标的手段是依赖倒转原则。抽象层次包含的是应用系统的商务逻辑和宏观的、对整个系统来说重要的战略性决定,是必然性的体现,那么抽象层次就应当是较为稳定的,应当是复用的重点;也应当是维护的重点;而具体层次则含有一些次要的与实现有关的算法和逻辑,以及战术性的决定,带有相当大的偶然性选择。具体层次的代码是会经常有变动的,不能避免出现错误。4、接口隔离原则(Interface Segregation Principle,或者ISP);使用多个专门的接口比使用单一的总接口要好。换言之,从一个客户类的角度讲:一个类对另一个类的依赖性应当是建立在最小的接口上的。接口隔离原则与迪米特法则(下面讲到)都是对一个软件实体与其他的软件实体的通信限制。迪米特原则要求尽可能地限制通信的宽度和深度,接品隔离原则要求通信的宽度尽可能地窄。这样做的结果使一个软件系统在功能扩展过程当中,不会将修改的压力传递到其他对象。一个接口相当于剧本中的一种角色,而此角色在一个舞台上由哪一个演员来演则相当于接口的实现。因此,一个接口应当简单地代表一个角色,而不是多个角色。如果系统涉及到多个角色的话,那么每一个角色都应当由一个特定的接口代表。5、组合/聚合复用原则(Composition/Aggregation Principle,或者CARP);组合/聚合原则就是在一个新的对象里面使用一些已有的对象,使之成为新对象的一部分;新的对象通过向这些对象的委派达到得复用已有功能的目的。要尽量使用组合/聚合,尽量不要使用继承。6、迪米特法则(Law of Demeter,或者LoD);一个软件实体应当尽可能少的与其他实体发生相互作用。模块之间的交互要少。这样做的结果是当系统的功能需要扩展时,会相对更容易地做到对修改的关闭。一个对象应当对其他对象有尽可能少的了解。7、单一职责原则(Single Responsibility Principle,或者SRP)在设计中为每种职责设计一个类,彼此保持正交,互不干涉。这个原则比较容易理解,这里不在多说。小结当我们掌握了C#的语法,当我们了解了面向对象的封装、继承、多态等特性,当我们可以用各种框架与技术构建桌面以及Web应用时,这并不意味着我们可以写出面向对象的程序,不意味着我们可以很好的实现代码复用,弹性维护,不意味着我们可以实现在维护、扩展基础上的代码复用。使用面向对象语言开发的程序不一定是面向对象的,使用面向过程的语言开发的程序也不一定不是面向对象的。要想开发出一个具有可维护性和可复用性的软件系统,那是需要优秀的设计和长时间的运行才能完成的,其实我们可以观察一下,任何一个优秀的软件产品都是经过长时间的设计,运行,维护,修改等最后才成为成功的产品,版本上也在不断的更新。衡量一个软件开发者是不是一个好的软件开发者,不是看他是否实现了软件的必要功能,而是要看你的软件在满足功能需求的情况下是否做到了复用性和可扩展性,这对于一个大型系统尤其重要。我们不要静止的看待一个软件,而一定要把软件过程放在时间轴上来观察与设计它,只有放在时间轴上经得住考验的软件系统才是成功的。软件的复用性和可扩展性对于大型系统是必要的,我们在设计自己的软件系统时,甚至在编写代码时更需要考虑一下这样做是否遵循了系统设计的原则,是否有利于系统的可维护性和可复用性,是否达到了常说的“高内聚低耦合”呢?设计模式正是解决这一问题的王道。从下文开始我们将结合实例对于GoF23种设计模式进行一一讲解。现在我们正式进入GoF23种设计模式中的创建型模式的讲解中来,创建型模式主要解决对象如何创建的问题,提倡创建对象的责任和使用对象的责任分离,以达到更好对创建对象的控制的目的,创建型模式主要包括抽象工厂(Abstract Factory),建造者(Builder),工厂方法(Factory Method),原型(Prototype),单子(Singleton)。这篇文章主要分为两大部分内容,在第一部分中我将介绍抽象工厂模式的原型,包括抽象工厂的意图,可以解决的问题,原型代码和UML等,再结合一个生活中的小例子进行原型的说明。第二部分我会结合实际项目来讲述一下抽象工厂模式是如何应用的。最后我会对抽象工厂模式进行一个小结。工厂模式的几种形态工厂模式专门负责将大量有共同接口的类实例化。工厂模式可以动态决定将哪一个类实例化,不必事先知道每次要实例化哪一个类。工厂模式有以下几种形态:简单工厂(Simple Factory)模式:又称静态工厂方法模式(Static Factory Method Pattern)。主要是工厂中提供一个静态的方法用来根据不同的参数创建不同的抽象产品的具体实例,一般在IoC中应用比较多,例如通过反射机制和简单工厂模式可以解决依赖注入的问题。简单工厂模式不属于GoF23种设计模式,这里也就不再作过多的分析。感兴趣的园友可以找找相关资料。工厂方法(Factory Method)模式:又称多态性工厂(Polymorphic Factory)模式或虚拟构造子(Virtual Constructor)模式。这个模式属于GoF23种设计模式之一,在后面的文章中会做详细的介绍。抽象工厂(Abstract Factory)模式:又称工具箱(Kit或Toolkit)模式。这是本文的重点。抽象工厂模式的原型描述:假设一个子系统需要一些产品对象,而这些产品对象又属于一个以上的产品等级结构。那么为了将消费这些产品的责任和创建这些产品对象的责任分割开来,可以引进抽象工厂模式。这样的话,消费产品的一方不需要直接参与产品的创建工作,而只需要向一个公用的工厂接口请求所需要的产品。意图:抽象工厂模式可以向客户端(Client指代码模式的使用者,后文类同)提供一个接口,使得客户端在不必指定产品的具体类型的情况下,创建多个产品族(Product Family指位于不同产品等级中,功能相关联的产品的集合)中的产品对象。模式原型UML:抽象工厂涉及到以下角色:抽象工厂(AbstractFactory )角色:声明一个操作集合的接口以创建抽象产品族。具体工厂(ConcreteFactory)角色:实现创建具体产品族的抽象工厂的实现。抽象产品(AbstractProduct )角色:声明一个产品的接口。具体产品(Product)角色:定义了一个被具体工厂创建的产品对象,实现了抽象工厂接口。客户端(Client)角色:使用抽象工厂和抽象产品的类。 模式原型代码:using System;namespace DesignPatterns.Creational / 测试程序 class MainApp public static void Main() / 抽象工厂1 AbstractFactory factory1 = new ConcreteFactory1(); Client c1 = new Client(factory1); c1.Run(); / 抽象工厂2 AbstractFactory factory2 = new ConcreteFactory2(); Client c2 = new Client(factory2); c2.Run(); / 等候用户输入 Console.Read(); / 抽象工厂 abstract class AbstractFactory public abstract AbstractProductA CreateProductA(); public abstract AbstractProductB CreateProductB(); / 具体工厂1 class ConcreteFactory1 : AbstractFactory public override AbstractProductA CreateProductA() return new ProductA1(); public override AbstractProductB CreateProductB() return new ProductB1(); / 具体工厂2 class ConcreteFactory2 : AbstractFactory public override AbstractProductA CreateProductA() return new ProductA2(); public override AbstractProductB CreateProductB() return new ProductB2(); / 抽象产品A,产品族中一个成员 abstract class AbstractProductA / 抽象产品B,产品族中一个成员 abstract class AbstractProductB public abstract void Interact(AbstractProductA a); / 具体产品A1 class ProductA1 : AbstractProductA / 具体产品B1 class ProductB1 : AbstractProductB public override void Interact(AbstractProductA a) Console.WriteLine(this.GetType().Name + interacts with + a.GetType().Name); / 具体产品A2 class ProductA2 : AbstractProductA / 具体产品B2 class ProductB2 : AbstractProductB public override void Interact(AbstractProductA a) Console.WriteLine(this.GetType().Name + interacts with + a.GetType().Name); / 客户端,使用环境 class Client private AbstractProductA AbstractProductA; private AbstractProductB AbstractProductB; / 构造,注意通过构造传入抽象工厂 public Client(AbstractFactory factory) AbstractProductB = factory.CreateProductB(); AbstractProductA = factory.CreateProductA(); public void Run() AbstractProductB.Interact(AbstractProductA); /抽象工厂模式原型代码usingSystem;namespaceDesignPatterns.Creational/测试程序classMainApppublicstaticvoidMain()/抽象工厂1AbstractFactoryfactory1=newConcreteFactory1();Clientc1=newClient(factory1);c1.Run();/抽象工厂2AbstractFactoryfactory2=newConcreteFactory2();Clientc2=newClient(factory2);c2.Run();/等候用户输入Console.Read();/抽象工厂abstractclassAbstractFactorypublicabstractAbstractProductACreateProductA();publicabstractAbstractProductBCreateProductB();/具体工厂1classConcreteFactory1:AbstractFactorypublicoverrideAbstractProductACreateProductA()returnnewProductA1();publicoverrideAbstractProductBCreateProductB()returnnewProductB1();/具体工厂2classConcreteFactory2:AbstractFactorypublicoverrideAbstractProductACreateProductA()returnnewProductA2();publicoverrideAbstractProductBCreateProductB()returnnewProductB2();/抽象产品A,产品族中一个成员abstractclassAbstractProductA/抽象产品B,产品族中一个成员abstractclassAbstractProductBpublicabstractvoidInteract(AbstractProductAa);/具体产品A1classProductA1:AbstractProductA/具体产品B1classProductB1:AbstractProductBpublicoverridevoidInteract(AbstractProductAa)Console.WriteLine(this.GetType().Name+interactswith+a.GetType().Name);/具体产品A2classProductA2:AbstractProductA/具体产品B2classProductB2:AbstractProductBpublicoverridevoidInteract(AbstractProductAa)Console.WriteLine(this.GetType().Name+interactswith+a.GetType().Name);/客户端,使用环境classClientprivateAbstractProductAAbstractProductA;privateAbstractProductBAbstractProductB;/构造,注意通过构造传入抽象工厂publicClient(AbstractFactoryfactory)AbstractProductB=factory.CreateProductB();AbstractProductA=factory.CreateProductA();publicvoidRun()AbstractProductB.Interact(AbstractProductA);输出结果为:ProductB1 interacts with ProductA1ProductB2 interacts with ProductA2 生活中的实例:这个生活中的实例代码演示了一个电脑游戏,在游戏中建立不同的动物世界会使用不同的工厂。虽然创建动物的大陆工厂是不同的,但是动物之间的相互关系保持不变。Real world code using Abstract Factory in C#using System;namespace DesignPatterns.Creational.AbstractFactory.RealWorld class MainApp public static void Main() / 创建非洲大陆 ContinentFactory africa = new AfricaFactory(); AnimalWorld world = new AnimalWorld(africa); world.RunFoodChain(); / 创建美洲大陆 ContinentFactory america = new AmericaFactory(); world = new AnimalWorld(america); world.RunFoodChain(); / 等待用户输入 Console.ReadKey(); / 抽象工厂 abstract class ContinentFactory / 创建食草动物 public abstract Herbivore CreateHerbivore(); / 创建食肉动物 public abstract Carnivore CreateCarnivore(); / 具体工厂1 class AfricaFactory : ContinentFactory public override Herbivore CreateHerbivore() / 返回牛羚 return new Wildebeest(); public override Carnivore CreateCarnivore() / 返回狮子 return new Lion(); / 具体工厂2 class AmericaFactory : ContinentFactory public override Herbivore CreateHerbivore() / 返回野牛 return new Bison(); public override Carnivore CreateCarnivore() / 返回狼 return new Wolf(); / 抽象产品A abstract class Herbivore / 抽象产品B abstract class Carnivore / 交互关系,食肉动物可以吃掉食草动物 public abstract void Eat(Herbivore h); / 具体产品A1 class Wildebeest : Herbivore / 具体产品B1 class Lion : Carnivore public override void Eat(Herbivore h) / 吃掉牛羚 Console.WriteLine(this.GetType().Name + eats + h.GetType().Name); / 具体产品A2 class Bison : Herbivore / 具体产品B2 class Wolf : Carnivore public override void Eat(Herbivore h) / 吃掉野牛 Console.WriteLine(this.GetType().Name + eats + h.GetType().Name); / 客户端 class AnimalWorld private Herbivore _herbivore; private Carnivore _carnivore; / 通过构造器传入具体工厂 public AnimalWorld(ContinentFactory factory) _carnivore = factory.CreateCarnivore(); _herbivore = factory.CreateHerbivore(); public void RunFoodChain() _carnivore.Eat(_herbivore); Real world code using Abstract Factory in C#usingSystem;namespaceDesignPatterns.Creational.AbstractFactory.RealWorldclassMainApppublicstaticvoidMain()/创建非洲大陆ContinentFactoryafrica=newAfricaFactory();AnimalWorldworld=newAnimalWorld(africa);world.RunFoodChain();/创建美洲大陆ContinentFactoryamerica=newAmericaFactory();world=newAnimalWorld(america);world.RunFoodChain();/等待用户输入Console.ReadKey();/抽象工厂abstractclassContinentFactory/创建食草动物publicabstractHerbivoreCreateHerbivore();/创建食肉动物publicabstractCarnivoreCreateCarnivore();/具体工厂1classAfricaFactory:ContinentFactorypublicoverrideHerbivoreCreateHerbivore()/返回牛羚returnnewWildebeest();publicoverrideCarnivoreCreateCarnivore()/返回狮子returnnewLion();/具体工厂2classAmericaFactory:ContinentFactorypublicoverrideHerbivoreCreateHerbivore()/返回野牛returnnewBison();publicoverrideCarnivoreCreateCarnivore()/返回狼returnnewWolf();/抽象产品AabstractclassHerbivore/抽象产品BabstractclassCarnivore/交互关系,食肉动物可以吃掉食草动物publicabstractvoidEat(Herbivoreh);/具体产品A1classWildebeest:Herbivore/具体产品B1classLion:CarnivorepublicoverridevoidEat(Herbivoreh)/吃掉牛羚Console.WriteLine(this.GetType().Name+eats+h.GetType().Name);/具体产品A2classBison:Herbivore/具体产品B2classWolf:CarnivorepublicoverridevoidEat(Herbivoreh)/吃掉野牛Console.WriteLine(this.GetType().Name+eats+h.GetType().Name);/客户端classAnimalWorldprivateHerbivore_herbivore;privateCarnivore_carnivore;/通过构造器传入具体工厂publicAnimalWorld(ContinentFactoryfactory)_carnivore=factory.CreateCarnivore();_herbivore=factory.CreateHerbivore();publicvoidRunFoodChain()_carnivore.Eat(_herbivore);输出的结果为:Lion eats WildebeestWolf eats Bison什么情况下使用抽象工厂:文献【GOF95】指出,在以下情况下应当考虑使用抽象工厂模式:1.一个系统不应当依赖于产品类实例如何被创建、组合和表达的细节,这对于所有形态的工厂模式都是重要的。2、这个系统的产品有多于一个产品族,而系统只消费其中某一个族的产品(上面这一条叫做抽象工厂模式的原始用意。)3、同属于同一个产品族的产品是在一起使用的,这一约束必须在系统的设计中体现出来。4、系统提供一个产品类的库,所有的产品以同样的接口实现,从而使客户端不依赖于实现。实际项目举例现在需要创建分属于不同操作系统的视窗构件。比如命令按钮(Button)与文本框(Text)等都是视窗构件,在UNIX系统的视窗环境和Windows操作系统的视窗环境中,这两个构件有不同的本地体现,它们的细节也有所不同。在每一个操作系统中,都有一
温馨提示
- 1. 本站所有资源如无特殊说明,都需要本地电脑安装OFFICE2007和PDF阅读器。图纸软件为CAD,CAXA,PROE,UG,SolidWorks等.压缩文件请下载最新的WinRAR软件解压。
- 2. 本站的文档不包含任何第三方提供的附件图纸等,如果需要附件,请联系上传者。文件的所有权益归上传用户所有。
- 3. 本站RAR压缩包中若带图纸,网页内容里面会有图纸预览,若没有图纸预览就没有图纸。
- 4. 未经权益所有人同意不得将文件中的内容挪作商业或盈利用途。
- 5. 人人文库网仅提供信息存储空间,仅对用户上传内容的表现方式做保护处理,对用户上传分享的文档内容本身不做任何修改或编辑,并不能对任何下载内容负责。
- 6. 下载文件中如有侵权或不适当内容,请与我们联系,我们立即纠正。
- 7. 本站不保证下载资源的准确性、安全性和完整性, 同时也不承担用户因使用这些下载资源对自己和他人造成任何形式的伤害或损失。
最新文档
- 宿舍说话检讨书
- 四川省宜宾市2024-2025学年七年级下学期期末考试数学试卷(含答案)
- 江苏省常州市钟楼区小学英语五年级下册期末检测卷(含答案)
- 《大数据审计基础》全套教学课件
- 安徽省阜阳市2024-2025学年八年级下学期期末教学质量检测数学试卷(含答案)
- 3D打印:从原理到创新应用知到智慧树答案
- 电商平台消费者隐私数据保护策略研究
- 餐饮行业外卖模式创新案例分析
- “四史”概论知到智慧树答案
- 水银泄露处理课件
- 公司适用法律法规标准清单2025年08月更新
- 国家中医药管理局《中医药事业发展“十五五”规划》全文
- 职业健康培训材料
- 新版食品安全法前后对比-讲义课件
- 《政治经济学》(全套课件)
- 武汉理工大学计算机科学与技术学院课程教学大纲
- 应急疏散培训试题
- QC080000-2017 HSF有害物质管理程序文件全套
- 计量操作人员(通用类)考试题库(含答案)
- 大海(张雨生)原版五线谱钢琴谱正谱乐谱
- 公开课第一课素描基础入门课件
评论
0/150
提交评论