设计模式
设计模式是对软件设计开发过程中反复出现的某类问题的通用解决方案。设计模式更多的是指导思想和方法论,而不是现成的代码,当然每种设计模式都有每种语言中的具体实现方式。学习设计模式更多的是理解各种模式的内在思想和解决的问题,毕竟这是前人无数经验总结成的最佳实践,而代码实现则是对加深理解的辅助。
设计模式的原则
单一职责原则
最少知识原则
开放——封闭原则
接口和面向接口编程
JavaScript 设计模式与开发实践 (曾探) (Z-Library)
- 单一职责 一个程序只做一件事,如果功能过于复杂就拆分开,每个部分保持独立
- 开发封闭 对扩展开放,对修改封闭 增加需求时,扩展新代码,而非修改旧代码
- 李氏置换 子类能覆盖父类(继承)
- 接口独立 类似于单一职责,关注于接口 保存接口的单一独立
- 依赖倒置 只关注接口而不关注具体类的实现
作者:靓仔一个 链接:https://juejin.cn/post/6994464022628139045 来源:稀土掘金 著作权归作者所有。商业转载请联系作者获得授权,非商业转载请注明出处。
设计模式的类型
结构型模式(Structural Patterns): 通过识别系统中组件间的简单关系来简化系统的设计。
创建型模式(Creational Patterns): 处理对象的创建,根据实际情况使用合适的方式创建对象。常规的对象创建方式可能会导致设计上的问题,或增加设计的复杂度。创建型模式通过以某种方式控制对象的创建来解决问题。
行为型模式(Behavioral Patterns): 用于识别对象之间常见的交互模式并加以实现,如此,增加了这些交互的灵活性。
前端需要了解的9种设计模式 什么是设计模式?设计模式的类型一. 结构型模式(Structural Patterns)二. 创建型模式(Creat-腾讯云开发者社区-腾讯云 (tencent.com)

设计模式的细分

设计模式的简单解析
一、创建型模式
- 单例模式(Singleton Pattern) 确保一个类只有一个实例,并提供全局访问点。
用于创建全局唯一的对象,例如全局状态管理器、全局配置对象等。
- 工厂方法模式(Factory Pattern) 定义一个创建对象的接口,但由子类决定实例化哪个类。
用于创建不同类型的对象,例如创建不同类型的组件、创建不同类型的数据请求等。
- 抽象工厂模式(Abstract Factory Pattern) 提供一个创建一系列相关或相互依赖对象的接口,而无需指定它们具体的类。
用于创建一系列相关或相互依赖的对象,例如创建一组相互关联的UI组件。
- 建造者模式(Builder Pattern) 将一个复杂对象的构建过程与其表示分离,使得同样的构建过程可以创建不同的表示。
用于构建复杂对象,例如构建复杂的表单、构建复杂的图表等。
- 原型模式(Prototype Pattern) 通过复制现有对象来创建新对象。
用于通过复制现有对象来创建新对象,例如通过克隆已有组件来创建新组件。
二、结构型模式 6. 适配器模式(Adapter Pattern) 将一个类的接口转换成客户希望的另外一个接口。
用于将一个类的接口转换成另一个类的接口,例如将不同数据源返回的数据格式统一成统一格式。
- 装饰器模式(Decorator Pattern) 动态地给一个对象添加一些额外的职责。
用于动态地给一个对象添加额外的职责,例如给某个组件添加日志记录功能。
- 外观模式(Facade Pattern) 为子系统中的一组接口提供一个统一的接口。
用于为子系统提供一个简化接口,例如封装复杂UI库提供简单易用的API。
- 桥接模式(Bridge Pattern) 将抽象部分与它们的实现部分分离,使它们可以独立地变化。
用于将抽象部分与实现部分分离,例如将UI组件的样式与行为分离。
- 组合模式(Composite Pattern) 将对象组合成树形结构以表示“部分-整体”的层次结构。
用于将对象组合成树形结构,例如构建复杂的导航菜单。
- 享元模式(Flyweight Pattern) 运用共享技术有效地支持大量细粒度的对象。
用于共享细粒度的对象,例如共享相同的图标、共享相同的样式等。
- 代理模式(Proxy Pattern) 用于控制对某个对象的访问。
例如对某个重要操作进行权限控制。
三、行为型模式 13. 策略模式(Strategy Pattern) 用于定义一系列算法,并使其可以互换。
例如根据不同条件选择不同的数据请求策略。
- 模板方法模式(Template Method Pattern) 定义一个操作中的算法骨架,而将一些步骤延迟到子类中实现。
用于定义一个算法骨架,而将一些步骤延迟到子类中实现,例如封装通用的数据请求流程。
- 观察者模式(Observer Pattern) 定义了对象之间的一对多依赖关系,当一个对象状态改变时,所有依赖它的对象都会得到通知并自动更新。
例如实现事件监听、数据更新通知等。
- 迭代器模式(Iterator Pattern) 提供一种方法顺序访问一个聚合对象中各个元素,而又不暴露其内部表示。
用于顺序访问聚合对象中的元素,例如遍历数组、遍历DOM节点等。
职责链模式(Chain of Responsibility Pattern) 用于解耦请求发送者和接收者之间的关系,例如处理表单验证、处理请求拦截等。
命令模式(Command Pattern) 将请求封装成一个对象,从而使得可以用不同的请求对客户进行参数化。
例如实现撤销/重做功能。
- 备忘录模式(Memento Pattern) 在不破坏封装性的前提下,捕获一个对象的内部状态,并在该对象之外保存这个状态,用于保存和恢复对象状态。
例如保存用户输入内容以便恢复。
- 状态模式(State Pattern) 允许对象在其内部状态改变时改变它的行为。
例如实现复杂的表单验证逻辑。
- 访问者模式(Visitor Pattern) 表示一个作用于某对象结构中的各元素的操作,可以在不改变这些元素的类的前提下定义新操作。
例如对DOM节点进行不同类型的操作。
- 中介者模式(Mediator Pattern) 用一个中介对象来封装一系列的对象交互,使得各个对象不需要显式地相互引用。
例如实现组件之间的通信。
- 解释器模式(Interpreter Pattern) 给定一个语言,定义它的文法的一种表示,并定义一个解释器,该解释器使用该表示来解释语言中的句子。
例如解析模板语言、解析正则表达式等。 ————————————————
版权声明:本文为博主原创文章,遵循 CC 4.0 BY-SA 版权协议,转载请附上原文出处链接和本声明。原文链接:https://blog.csdn.net/csdn9_14/article/details/135011433
设计模式的详细解析
单例模式
单例模式是一种常用的模式,有一些对象我们往往只需要一个,比如线程池、全局缓存、浏览器中的 window对象等。在 JavaScript开发中,单例模式的用途同样非常广泛。试想一下,当我们单击登录按钮的时候,页面中会出现一个登录浮窗,而这个登录浮窗是唯一的,无论单击多少次登录按钮,这个浮窗都只会被创建一次,那么这个登录浮窗就适合用单例模式来创建。
仅获得唯一的一个实例,比如我们需要一个登录弹窗,因为我们点击多少次登录按钮,都应该只有一个登录弹窗。所以登录弹窗是唯一的,因为我们有的时候不用登录,所以不应该提前加载弹窗节点并隐藏,所以我们需要用到动态创建唯一实例这种设计模式。
策略模式
策略模式是一种常用且有效的设计模式,本章提供了计算奖金、缓动动画、表单校验这三个例子来加深大家对策略模式的理解。从这三个例子中,我们可以总结出策略模式的一些优点。
策略模式利用组合、委托和多态等技术和思想,可以有效地避免多重条件选择语句。
策略模式提供了对开放—封闭原则的完美支持,将算法封装在独立的 strategy中,使得它们易于切换,易于理解,易于扩展。
策略模式中的算法也可以复用在系统的其他地方,从而避免许多重复的复制粘贴工作。
在策略模式中利用组合和委托来让 Context拥有执行算法的能力,这也是继承的一种更轻便的替代方案。
当然,策略模式也有一些缺点,但这些缺点并不严重。
首先,使用策略模式会在程序中增加许多策略类或者策略对象,但实际上这比把它们负责的逻辑堆砌在 Context中要好。
其次,要使用策略模式,必须了解所有的 strategy,必须了解各个 strategy之间的不同点,这样才能选择一个合适的 strategy。比如,我们要选择一种合适的旅游出行路线,必须先了解选择飞机、火车、自行车等方案的细节。此时 strategy要向客户暴露它的所有实现,这是违反最少知识原则的。
就是说我们在设计的时候就需要知道方案的具体细节和不同,不同处使用参数或者策略的不同来插入。也就是说:
- 策略的不同,使用不同的函数来调(差异大的代码逻辑不同)(表单验证策略)
- 信息的不同,使用不同的参数来调(同质的代码,不同的入参)(错误信息)
代理模式

保护代理和虚拟代理
虽然这只是个虚拟的例子,但我们可以从中找到两种代理模式的身影。代理 B可以帮助 A过滤掉一些请求,比如送花的人中年龄太大的或者没有宝马的,这种请求就可以直接在代理 B处被拒绝掉。这种代理叫作保护代理。A和 B一个充当白脸,一个充当黑脸。白脸 A继续保持良好的女神形象,不希望直接拒绝任何人,于是找了黑脸 B来控制对 A的访问。
另外,假设现实中的花价格不菲,导致在程序世界里,new Flower也是一个代价昂贵的操作,那么我们可以把 new Flower的操作交给代理 B去执行,代理 B会选择在 A心情好时再执行 new Flower,这是代理模式的另一种形式,叫作虚拟代理。虚拟代理把一些开销很大的对象,延迟到真正需要它的时候才去创建。
其他代理模式
代理模式的变体种类非常多,限于篇幅及其在 JavaScript中的适用性,本章只简约介绍一下这些代理,就不一一详细展开说明了。
防火墙代理:控制网络资源的访问,保护主题不让“坏人”接近。
远程代理:为一个对象在不同的地址空间提供局部代表,在 Java中,远程代理可以是另一个虚拟机中的对象。
保护代理:用于对象应该有不同访问权限的情况。
智能引用代理:取代了简单的指针,它在访问对象时执行一些附加操作,比如计算一个对象被引用的次数。
写时复制代理:通常用于复制一个庞大对象的情况。写时复制代理延迟了复制的过程,当对象被真正修改时,才对它进行复制操作。写时复制代理是虚拟代理的一种变体,DLL(操作系统中的动态链接库)是其典型运用场景。
迭代器模式
迭代器模式和策略模式挺像。区别在于策略模式还暴露了方法内的一些接口给参数用,而迭代器只需要选择不同的策略。
迭代器模式是指提供一种方法顺序访问一个聚合对象中的各个元素,而又不需要暴露该对象的内部表示。迭代器模式可以把迭代的过程从业务逻辑中分离出来,在使用迭代器模式之后,即使不关心对象的内部构造,也可以按顺序访问其中的每个元素。
发布—订阅模式(观察者模式)
事件的绑定就是一种发布订阅,
本章我们学习了发布—订阅模式,也就是常说的观察者模式。发布—订阅模式在实际开发中非常有用。
发布—订阅模式的优点非常明显,一为时间上的解耦,二为对象之间的解耦。它的应用非常广泛,既可以用在异步编程中,也可以帮助我们完成更松耦合的代码编写。发布—订阅模式还可以用来帮助实现一些别的设计模式,比如中介者模式。从架构上来看,无论是 MVC还是 MVVM,都少不了发布—订阅模式的参与,而且 JavaScript本身也是一门基于事件驱动的语言。
当然,发布—订阅模式也不是完全没有缺点。创建订阅者本身要消耗一定的时间和内存,而且当你订阅一个消息后,也许此消息最后都未发生,但这个订阅者会始终存在于内存中。另外,发布—订阅模式虽然可以弱化对象之间的联系,但如果过度使用的话,对象和对象之间的必要联系也将被深埋在背后,会导致程序难以跟踪维护和理解。特别是有多个发布者和订阅者嵌套到一起的时候,要跟踪一个 bug不是件轻松的事情。
命令模式
做了一堆命令,和队列
组合模式
组合模式如果运用得当,可以大大简化客户的代码。一般来说,组合模式适用于以下这两种情况。
表示对象的部分-整体层次结构。组合模式可以方便地构造一棵树来表示对象的部分-整体结构。特别是我们在开发期间不确定这棵树到底存在多少层次的时候。在树的构造最终完成之后,只需要通过请求树的最顶层对象,便能对整棵树做统一的操作。在组合模式中增加和删除树的节点非常方便,并且符合开放-封闭原则。
客户希望统一对待树中的所有对象。组合模式使客户可以忽略组合对象和叶对象的区别,客户在面对这棵树的时候,不用关心当前正在处理的对象是组合对象还是叶对象,也就不用写一堆 if、else语句来分别处理它们。组合对象和叶对象会各自做自己正确的事情,这是组合模式最重要的能力。
本章我们了解了组合模式在 JavaScript开发中的应用。组合模式可以让我们使用树形方式创建对象的结构。我们可以把相同的操作应用在组合对象和单个对象上。在大多数情况下,我们都可以忽略掉组合对象和单个对象之间的差别,从而用一致的方式来处理它们。
然而,组合模式并不是完美的,它可能会产生一个这样的系统:系统中的每个对象看起来都与其他对象差不多。它们的区别只有在运行的时候会才会显现出来,这会使代码难以理解。此外,如果通过组合模式创建了太多的对象,那么这些对象可能会让系统负担不起。
模板方法模式
模板方法模式是一种典型的通过封装变化提高系统扩展性的设计模式。在传统的面向对象语言中,一个运用了模板方法模式的程序中,子类的方法种类和执行顺序都是不变的,所以我们把这部分逻辑抽象到父类的模板方法里面。而子类的方法具体怎么实现则是可变的,于是我们把这部分变化的逻辑封装到子类中。通过增加新的子类,我们便能给系统增加新的功能,并不需要改动抽象父类以及其他子类,这也是符合开放-封闭原则的。
但在 JavaScript中,我们很多时候都不需要依样画瓢地去实现一个模版方法模式,高阶函数是更好的选择。
享元模式(flyweight)
内部状态存储于对象内部。
内部状态可以被一些对象共享。
内部状态独立于具体的场景,通常不会改变。
外部状态取决于具体的场景,并根据场景而变化,外部状态不能被共享。
剥离了外部状态的对象成为共享对象,外部状态在必要时被传入共享对象来组装成一个完整的对象。虽然组装外部状态成为一个完整对象的过程需要花费一定的时间,但却可以大大减少系统中的对象数量,相比之下,这点时间或许是微不足道的。因此,享元模式是一种用时间换空间的优化模式。
在上面的例子中,性别是内部状态,内衣是外部状态,通过区分这两种状态,大大减少了系统中的对象数量。通常来讲,内部状态有多少种组合,系统中便最多存在多少个对象,因为性别通常只有男女两种,所以该内衣厂商最多只需要 2个对象。
使用享元模式的关键是如何区别内部状态和外部状态。可以被对象共享的属性通常被划分为内部状态,如同不管什么样式的衣服,都可以按照性别不同,穿在同一个男模特或者女模特身上,模特的性别就可以作为内部状态储存在共享对象的内部。而外部状态取决于具体的场景,并根据场景而变化,就像例子中每件衣服都是不同的,它们不能被一些对象共享,因此只能被划分为外部状态。
适用性
享元模式是一种很好的性能优化方案,但它也会带来一些复杂性的问题,从前面两组代码的比较可以看到,使用了享元模式之后,我们需要分别多维护一个 factory对象和一个 manager对象,在大部分不必要使用享元模式的环境下,这些开销是可以避免的。
享元模式带来的好处很大程度上取决于如何使用以及何时使用,一般来说,以下情况发生时便可以使用享元模式。
一个程序中使用了大量的相似对象。
由于使用了大量对象,造成很大的内存开销。
对象的大多数状态都可以变为外部状态。
剥离出对象的外部状态之后,可以用相对较少的共享对象取代大量对象。
可以看到,文件上传的例子完全符合这四点。
factory对象是工厂对象,用于创建upload实例。manager对象是管理者,就是享元对象的管理者,它调用工厂创建upload实例(add方法),并配置delete方法等。还有一个特殊的setExternalState方法用于给享元对象填入共享的外部状态。
职责链模式
在手机商城的例子中,本来我们要被迫维护一个充斥着条件分支语句的巨大的函数,在例子里的购买过程中只打印了一条 log语句。其实在现实开发中,这里要做更多事情,比如根据订单种类弹出不同的浮层提示、渲染不同的 UI节点、组合不同的参数发送给不同的 cgi等。用了职责链模式之后,每种订单都有各自的处理函数而互不影响。
其次,使用了职责链模式之后,链中的节点对象可以灵活地拆分重组。增加或者删除一个节点,或者改变节点在链中的位置都是轻而易举的事情。这一点我们也已经看到,在上面的例子中,增加一种订单完全不需要改动其他订单函数中的代码。
职责链模式还有一个优点,那就是可以手动指定起始节点,请求并不是非得从链中的第一个节点开始传递。比如在公交车的例子中,如果我明确在我前面的第一个人不是售票员,那我当然可以越过他把公交卡递给他前面的人,这样可以减少请求在链中的传递次数,更快地找到合适的请求接受者。这在普通的条件分支语句下是做不到的,我们没有办法让请求越过某一个 if判断。
拿代码来证明这一点,假设某一天网站中支付过定金的订单已经全部结束购买流程,我们在接下来的时间里只需要处理普通购买订单,所以我们可以直接把请求交给普通购买订单节点:
orderNormal.passRequest( 1, false, 500); //普通购买,无优惠券如果运用得当,职责链模式可以很好地帮助我们组织代码,但这种模式也并非没有弊端,首先我们不能保证某个请求一定会被链中的节点处理。比如在期末考试的例子中,小纸条上的题目也许没有任何一个同学知道如何解答,此时的请求就得不到答复,而是径直从链尾离开,或者抛出一个错误异常。在这种情况下,我们可以在链尾增加一个保底的接受者节点来处理这种即将离开链尾的请求。
另外,职责链模式使得程序中多了一些节点对象,可能在某一次的请求传递过程中,大部分节点并没有起到实质性的作用,它们的作用仅仅是让请求传递下去,从性能方面考虑,我们要避免过长的职责链带来的性能损耗。
职责链模式也是链式的,一定程度上可以替代迭代器。
中介者模式
中介者模式是迎合迪米特法则的一种实现。迪米特法则也叫最少知识原则,是指一个对象应该尽可能少地了解另外的对象(类似不和陌生人说话)。如果对象之间的耦合性太高,一个对象发生改变之后,难免会影响到其他的对象,跟“城门失火,殃及池鱼”的道理是一样的。而在中介者模式里,对象之间几乎不知道彼此的存在,它们只能通过中介者对象来互相影响对方。
因此,中介者模式使各个对象之间得以解耦,以中介者和对象之间的一对多关系取代了对象之间的网状多对多关系。各个对象只需关注自身功能的实现,对象之间的交互关系交给了中介者对象来实现和维护。
不过,中介者模式也存在一些缺点。其中,最大的缺点是系统中会新增一个中介者对象,因为对象之间交互的复杂性,转移成了中介者对象的复杂性,使得中介者对象经常是巨大的。中介者对象自身往往就是一个难以维护的对象。
我们都知道,毒贩子虽然使吸毒者和制毒者之间的耦合度降低,但毒贩子也要抽走一部分利润。同样,在程序中,中介者对象要占去一部分内存。而且毒贩本身还要防止被警察抓住,因为它了解整个犯罪链条中的所有关系,这表明中介者对象自身往往是一个难以维护的对象。
中介者模式可以非常方便地对模块或者对象进行解耦,但对象之间并非一定需要解耦。在实际项目中,模块或对象之间有一些依赖关系是很正常的。毕竟我们写程序是为了快速完成项目交付生产,而不是堆砌模式和过度设计。关键就在于如何去衡量对象之间的耦合程度。一般来说,如果对象之间的复杂耦合确实导致调用和维护出现了困难,而且这些耦合度随项目的变化呈指数增长曲线,那我们就可以考虑用中介者模式来重构代码。
装饰者模式
本章通过数据上报、统计函数的执行时间、动态改变函数参数以及插件式的表单验证这 4个例子,我们了解了装饰函数,它是 JavaScript中独特的装饰者模式。这种模式在实际开发中非常有用,除了上面提到的例子,它在框架开发中也十分有用。作为框架作者,我们希望框架里的函数提供的是一些稳定而方便移植的功能,那些个性化的功能可以在框架之外动态装饰上去,这可以避免为了让框架拥有更多的功能,而去使用一些 if、else语句预测用户的实际需要。
状态模式
React、状态机
适配器模式
解决接口不匹配问题
练习题
【设计模式专题之单例模式】1.小明的购物车
时间限制:1.000S 空间限制:256MB
题目描述
小明去了一家大型商场,拿到了一个购物车,并开始购物。请你设计一个购物车管理器,记录商品添加到购物车的信息(商品名称和购买数量),并在购买结束后打印出商品清单。(在整个购物过程中,小明只有一个购物车实例存在)。
输入描述
输入包含若干行,每行包含两部分信息,分别是商品名称和购买数量。商品名称和购买数量之间用空格隔开。
输出描述
输出包含小明购物车中的所有商品及其购买数量。每行输出一种商品的信息,格式为 "商品名称 购买数量"。
输入示例
Apple 3
Banana 2
Orange 5输出示例
Apple 3
Banana 2
Orange 5提示信息
本道题目请使用单例设计模式: 使用私有静态变量来保存购物车实例。 使用私有构造函数防止外部直接实例化。
题解
// 处理用户输入
const readline = require('readline');
const rl = readline.createInterface({
input: process.stdin,
output: process.stdout
});
// 存储用户输入
const userInput = [];
rl.on('line', function(line) {
userInput.push(line.trim()); // 移除每行输入的前后空白字符
});
// 处理输入结束
rl.on('close', function() {
// 获取购物车实例
const cartManager = ShoppingCart.getInstance();
// 处理输入数据
userInput.forEach(input => {
const [product, quantity] = input.split(" ");
cartManager.addProduct(product, parseInt(quantity, 10));
});
// 购买结束,打印商品清单
cartManager.printCart();
console.log('\n'); // 添加换行符,美观输出
process.exit(0); // 结束程序
});
// 私有构造函数
class ShoppingCart {
constructor() {
this.cart = {};
}
// 添加商品到购物车
addProduct(product, quantity) {
if (this.cart[product]) {
this.cart[product] += quantity;
} else {
this.cart[product] = quantity;
}
}
// 打印购物车中的所有商品及其购买数量
printCart() {
for (let product in this.cart) {
if (this.cart.hasOwnProperty(product)) {
console.log(`${product} ${this.cart[product]}`);
}
}
}
}
// 单例实例的私有静态变量
ShoppingCart.instance = null;
// 公有静态方法,用于获取单例实例
ShoppingCart.getInstance = function() {
if (ShoppingCart.instance === null) {
ShoppingCart.instance = new ShoppingCart();
}
return ShoppingCart.instance;
};const readline = require('readline')
const r1 = readline.createInterface({
input: process.stdin,
output: process.stdout
})
const userInput = []
r1.on('line', (line) => {
userInput.push(line)
})
r1.on('close', () => {
main()
process.exit(0)
})
function main() {
const cartManager = ShoppingCart.getInstance()
// 处理用户输入
userInput.forEach(item => cartManager.addProduct(item.split(' ')[0], item.split(' ')[1]))
// 调用输出
cartManager.printProduct()
}
class ShoppingCart {
constructor() {
if(ShoppingCart.instance) {
return ShoppingCart.instance
}
this.cart = {}
ShoppingCart.instance = this
}
addProduct(product, quantity) {
if(this.cart[product]) {
this.cart[product] += quantity
} else {
this.cart[product] = quantity
}
}
printProduct() {
for(let [key, value] of Object.entries(this.cart)) {
console.log(`${key} ${value}`)
}
}
static getInstance() {
return this.instance || new ShoppingCart()
}
}【设计模式专题之工厂方法模式】2.积木工厂
时间限制:1.000S 空间限制:256MB
题目描述
小明家有两个工厂,一个用于生产圆形积木,一个用于生产方形积木,请你帮他设计一个积木工厂系统,记录积木生产的信息。
输入描述
输入的第一行是一个整数 N(1 ≤ N ≤ 100),表示生产的次数。
接下来的 N 行,每行输入一个字符串和一个整数,字符串表示积木的类型。积木类型分为 "Circle" 和 "Square" 两种。整数表示该积木生产的数量
输出描述
对于每个积木,输出一行字符串表示该积木的信息。
输入示例
3
Circle 1
Square 2
Circle 1输出示例
Circle Block
Square Block
Square Block
Circle Block提示信息
在示例中,积木工厂生产了3块积木,其中有2块是圆形积木,1块是方形积木。根据输入的类型,每块积木的信息被输出到控制台。
题解
const readline = require('readline')
const r1 = readline.createInterface({
input: process.stdin,
output: process.stdout
})
let userLine
const userInput = []
r1.on('line', (line) => {
if(line.length === 1) {
userLine = parseInt(line)
} else {
userInput.push(line)
}
})
r1.on('close', () => {
main()
process.exit(0)
})
function main() {
const blockFactory = new BlockFactory()
for(let i = 0; i < userLine; i++) {
let [type, num] = userInput[i].split(' ')
while(num--) {
blockFactory.createBlock(type)
}
}
}
class Block {
render() {
}
}
class CircleBlock extends Block{
render() {
console.log(`Circle Block`)
}
}
class SquareBlock extends Block{
render() {
console.log(`Square Block`)
}
}
class BlockFactory {
createBlock(type) {
switch (type) {
case 'Circle':
return new CircleBlock().render()
case 'Square':
return new SquareBlock().render()
default:
}
}
}【设计模式专题之抽象工厂模式】3. 家具工厂
时间限制:1.000S 空间限制:256MB
题目描述
小明家新开了两个工厂用来生产家具,一个生产现代风格的沙发和椅子,一个生产古典风格的沙发和椅子,现在工厂收到了一笔订单,请你帮他设计一个系统,描述订单需要生产家具的信息。
输入描述
输入的第一行是一个整数 N(1 ≤ N ≤ 100),表示订单的数量。
接下来的 N 行,每行输入一个字符串,字符串表示家具的类型。家具类型分为 "modern" 和 "classical" 两种。
输出描述
对于每笔订单,输出字符串表示该订单需要生产家具的信息。
modern订单会输出下面两行字符串
modern chair
modern sofa
classical订单会输出下面两行字符串
classical chair
classical soft输入示例
3
modern
classical
modern输出示例
modern chair
modern sofa
classical chair
classical sofa
modern chair
modern sofa提示信息
在示例中,工厂收到了3笔订单,其中有2笔要求生产modern风格,1笔要求生产classical风格。根据输入的类型,每次订单生产的家具信息被输出到控制台上。
题解
const readline = require('readline')
const r1 = readline.createInterface({
input: process.stdin,
output: process.stdout
})
let userLine
const userInput = []
r1.on('line', (line) => {
if(line.length === 1) {
userLine = parseInt(line)
} else {
userInput.push(line)
}
})
r1.on('close', () => {
main()
process.exit(0)
})
function main() {
userInput.forEach(style => {
const factory = getFurnitureFactory(style)
const chair = factory.createChair()
const sofa = factory.createSofa()
chair.make()
sofa.make()
})
}
function getFurnitureFactory(style) {
if(style === 'modern') {
return new ModernFurnitureFactory()
} else {
return new ClassicalFurnitureFactory()
}
}
class Chair{
make() {
}
}
class Sofa{
make(){
}
}
class ModernChair extends Chair{
make() {
console.log(`modern chair`)
}
}
class ModernSofa extends Sofa{
make() {
console.log(`modern sofa`)
}
}
class ClassicalChair extends Chair {
make() {
console.log(`classical chair`)
}
}
class ClassicalSofa extends Sofa {
make() {
console.log(`classical sofa`)
}
}
class FurnitureFactory {
createChair() {
}
createSofa() {
}
}
class ModernFurnitureFactory extends FurnitureFactory {
createChair() {
return new ModernChair()
}
createSofa() {
return new ModernSofa()
}
}
class ClassicalFurnitureFactory extends FurnitureFactory {
createChair() {
return new ClassicalChair()
}
createSofa() {
return new ClassicalSofa()
}
}【设计模式专题之建造者模式】4. 自行车加工
时间限制:1.000S 空间限制:256MB
题目描述
小明家新开了一家自行车工厂,用于使用自行车配件(车架 frame 和车轮 tires )进行组装定制不同的自行车,包括山地车和公路车。
山地车使用的是Aluminum Frame(铝制车架)和 Knobby Tires(可抓地轮胎),公路车使用的是 Carbon Frame (碳车架)和 Slim Tries。
现在它收到了一笔订单,要求定制一批自行车,请你使用【建造者模式】告诉小明这笔订单需要使用那些自行车配置吧。
输入描述
输入的第一行是一个整数 N(1 ≤ N ≤ 100),表示订单的数量。
接下来的 N 行,每行输入一个字符串,字符串表示客户的自行车需求。
字符串可以包含关键词 "mountain" 或 "road",表示客户需要山地自行车或公路自行车。
输出描述
对于每笔订单,输出该订单定制的自行车配置。
输入示例
3
mountain
road
mountain输出示例
Aluminum Frame Knobby Tires
Carbon Frame Slim Tires
Aluminum Frame Knobby Tires提示信息
在本例中:产品为自行车,可以有两个建造者:山地车建造者和公路车建造者。
题解
const readline = require('readline')
const r1 = readline.createInterface({
input: process.stdin,
output: process.stdout
})
let userLine
const userInput = []
r1.on('line', (line) => {
if(line.length === 1) {
userLine = parseInt(line)
} else {
userInput.push(line)
}
})
r1.on('close', () => {
main()
process.exit(0)
})
function main() {
userInput.forEach(item => {
const bike = getBike(item).addFrame().addLink().addTires().build()
bike.createBike()
})
}
function getBike(type) {
if(type === 'mountain') {
return new MontainBikeBuilder()
} else {
return new RoadBikeBuilder()
}
}
class MontainBikeBuilder {
constructor() {
this.bike = ''
}
addFrame() {
this.bike += 'Aluminum Frame'
return this
}
addLink() {
this.bike += ' '
return this
}
addTires() {
this.bike += 'Knobby Tires'
return this
}
build() {
return new Bike(this.bike)
}
}
class RoadBikeBuilder {
constructor() {
this.bike = ''
}
addFrame() {
this.bike += 'Carbon Frame'
return this
}
addLink() {
this.bike += ' '
return this
}
addTires() {
this.bike += 'Slim Tires'
return this
}
build() {
return new Bike(this.bike)
}
}
class Bike {
constructor(bike) {
this.bike = bike
}
createBike() {
console.log(this.bike)
}
}【设计模式专题之原型模式】5. 矩形原型
时间限制:1.000S 空间限制:256MB
题目描述
公司正在开发一个图形设计软件,其中有一个常用的图形元素是矩形。设计师在工作时可能需要频繁地创建相似的矩形,而这些矩形的基本属性是相同的(颜色、宽度、高度),为了提高设计师的工作效率,请你使用原型模式设计一个矩形对象的原型。使用该原型可以快速克隆生成新的矩形对象。
输入描述
首先输入一个字符串,表示矩形的基本属性信息,包括颜色、长度和宽度,用空格分隔,例如 "Red 10 5"。
然后输入一个整数 N(1 ≤ N ≤ 100),表示使用原型创建的矩形数量。
输出描述
对于每个矩形,输出一行字符串表示矩形的详细信息,如 "Color: Red, Width: 10,Height: 5"。
输入示例
Red 10 5
3输出示例
Color: Red, Width: 10, Height: 5
Color: Red, Width: 10, Height: 5
Color: Red, Width: 10, Height: 5提示信息
使用原型模式中的克隆方法实现矩形对象的创建。
题解
const readline = require('readline')
const r1 = readline.createInterface({
input: process.stdin,
output: process.stdout
})
let userLine
const userInput = []
r1.on('line', (line) => {
if(!userInput.length) {
userInput.push(line)
} else {
userLine = parseInt(line)
}
})
r1.on('close', () => {
main()
process.exit(0)
})
function main() {
let rectangle
while(userLine--) {
if (!rectangle) {
const [color, width, height] = userInput[0].split(' ')
rectangle = new Rectangle(color, width, height)
rectangle.descript()
}else {
const cloneRectangle = rectangle.clone()
cloneRectangle.descript()
}
}
}
class Rectangle {
constructor(color, width, height) {
this.color = color
this.width = width
this.height = height
}
clone() {
return new Rectangle(this.color, this.width, this.height)
}
descript() {
console.log(`Color: ${this.color}, Width: ${this.width}, Height: ${this.height}`)
}
}【设计模式专题之适配器模式】6. 扩展坞
时间限制:1.000S 空间限制:256MB
题目描述
小明购买了一台新电脑,该电脑使用 TypeC 接口,他已经有了一个USB接口的充电器和数据线,为了确保新电脑可以使用现有的USB接口充电器和数据线,他购买了一个TypeC到USB的扩展坞。
请你使用适配器模式设计并实现这个扩展坞系统,确保小明的新电脑既可以通过扩展坞使用现有的USB接口充电线和数据线,也可以使用TypeC接口充电。
输入描述
题目包含多行输入,第一行输入一个数字 N (1 < N <= 20),表示后面有N组测试数据。
之后N行都是一个整数,1表示使用电脑本身的TypeC接口,2表示使用扩展坞的USB接口充电。
输出描述
根据每行输入,输出相应的充电信息。
输入示例
3
1
2
1输出示例
TypeC
USB Adapter
TypeC题解
const readline = require('readline')
const r1 = readline.createInterface({
input: process.stdin,
output: process.stdout
})
let userLine
const userInput = []
r1.on('line', (line) => {
if (!userLine) {
userLine = parseInt(line)
} else {
userInput.push(parseInt(line))
}
})
r1.on('close', () => {
main()
process.exit(0)
})
function main() {
userInput.forEach(type => {
const charger = getCharger(type)
charger.charge()
})
}
function getCharger(type) {
if(type === 1) {
return new TypeCCharger()
} else {
return new USBAdapter(new USBCharger())
}
}
class Charger {
charge() {
throw new Error('原有的充电方法,不能直接调用')
}
}
class TypeCCharger extends Charger {
charge() {
console.log('TypeC')
}
}
class USBCharger extends Charger {
charge() {
console.log('USB Adapter')
}
}
class USBAdapter extends Charger {
constructor(usbCharger) {
super()
this.usbCharger = usbCharger
}
charge() {
this.usbCharger.charge()
}
}【设计模式专题之代理模式】7-小明买房子
时间限制:1.000S 空间限制:256MB
题目描述
小明想要购买一套房子,他决定寻求一家房屋中介来帮助他找到一个面积超过100平方米的房子,只有符合条件的房子才会被传递给小明查看。
输入描述
第一行是一个整数 N(1 ≤ N ≤ 100),表示可供查看的房子的数量。
接下来的 N 行,每行包含一个整数,表示对应房子的房屋面积。
输出描述
对于每个房子,输出一行,表示是否符合购房条件。如果房屋面积超过100平方米,输出 "YES";否则输出 "NO"。
输入示例
3
120
80
110输出示例
YES
NO
YES题解
const readline = require('readline')
const r1 = readline.createInterface({
input: process.stdin,
output: process.stdout
})
let userLine
const userInput = []
r1.on('line', (line) => {
if (!userLine) {
userLine = parseInt(line)
} else {
userInput.push(parseInt(line))
}
})
r1.on('close', () => {
main()
process.exit(0)
})
function main() {
userInput.forEach(area => {
const house = new House(area)
const agent = new Agent(new XiaoMing())
const result = agent.listenArea(house.area)
agent.client.getAnswer(result)
})
}
class House {
constructor(area) {
this.area = area
}
}
class Agent{
constructor(client) {
this.client = client
}
listenArea(area) {
if(area > 100) {
return 'YES'
} else {
return 'NO'
}
}
}
class XiaoMing {
constructor() {
this.agent = null
}
setAgent(agent) {
this.agent = agent
}
getAnswer(ans) {
console.log(ans)
}
}【设计模式专题装饰模式】8-咖啡加糖
时间限制:1.000S 空间限制:256MB
题目描述
小明喜欢品尝不同口味的咖啡,他发现每种咖啡都可以加入不同的调料,比如牛奶、糖和巧克力。他决定使用装饰者模式制作自己喜欢的咖啡。
请设计一个简单的咖啡制作系统,使用装饰者模式为咖啡添加不同的调料。系统支持两种咖啡类型:黑咖啡(Black Coffee)和拿铁(Latte)。
输入描述
多行输入,每行包含两个数字。第一个数字表示咖啡的选择(1 表示黑咖啡,2 表示拿铁),第二个数字表示要添加的调料类型(1 表示牛奶,2 表示糖)。
输出描述
根据每行输入,输出制作咖啡的过程,包括咖啡类型和添加的调料。
输入示例
1 1
2 2输出示例
Brewing Black Coffee
Adding Milk
Brewing Latte
Adding Sugar题解
const readline = require('readline')
const r1 = readline.createInterface({
input: process.stdin,
output: process.stdout
})
let userLine
const userInput = []
r1.on('line', (line) => {
userInput.push(line)
})
r1.on('close', () => {
main()
process.exit(0)
})
function main() {
userInput.forEach(item => {
const [t1, t2] = item.split(' ').map(item => parseInt(item))
const originCoffee = new OriginCoffee()
const coffeeType = new CoffeeType(originCoffee)
const spicesType = new SpicesType(originCoffee)
coffeeType.chooseCoffee(t1)
spicesType.chooseSpices(t2)
})
}
class OriginCoffee {
}
class CoffeeType extends OriginCoffee {
constructor(daddy) {
super()
this.daddy = daddy
}
chooseCoffee(type) {
if (type === 1) {
console.log('Brewing Black Coffee')
} else {
console.log('Brewing Latte')
}
}
}
class SpicesType extends OriginCoffee {
constructor(daddy) {
super()
this.daddy = daddy
}
chooseSpices(type) {
if (type === 1) {
console.log('Adding Milk')
} else {
console.log('Adding Sugar')
}
}
}// 咖啡组件接口
class Coffee {
brew() {
throw new Error('brew 方法未实现');
}
}
// 黑咖啡和拿铁实现咖啡组件接口
class BlackCoffee extends Coffee {
brew() {
console.log('Brewing Black Coffee');
return this;
}
}
class Latte extends Coffee {
brew() {
console.log('Brewing Latte');
return this;
}
}
// 调料装饰者接口,也实现咖啡组件接口
class CondimentDecorator extends Coffee {
constructor(coffee) {
super();
this.coffee = coffee;
}
brew() {
this.coffee.brew();
return this;
}
}
// 牛奶调料装饰者
class Milk extends CondimentDecorator {
addMilk() {
console.log('Adding Milk');
return this;
}
brew() {
this.addMilk();
return super.brew();
}
}
// 糖调料装饰者
class Sugar extends CondimentDecorator {
addSugar() {
console.log('Adding Sugar');
return this;
}
brew() {
this.addSugar();
return super.brew();
}
}
// 读取输入并制作咖啡
const readline = require('readline');
const rl = readline.createInterface({
input: process.stdin,
output: process.stdout
});
const coffeeMap = new Map([
[1, BlackCoffee],
[2, Latte]
]);
rl.on('line', function(input) {
const [coffeeType, condimentType] = input.split(' ').map(Number);
const coffee = new coffeeMap.get(coffeeType)();
let decoratedCoffee = coffee;
if (condimentType === 1) {
decoratedCoffee = new Milk(decoratedCoffee);
} else if (condimentType === 2) {
decoratedCoffee = new Sugar(decoratedCoffee);
}
decoratedCoffee.brew();
}).on('close', function() {
console.log('\n'); // 添加换行符,美观输出
process.exit(0);
});【设计模式专题之外观模式】9-电源开关
时间限制:1.000S 空间限制:256MB
题目描述
小明家的电源总开关控制了家里的三个设备:空调、台灯和电视机。每个设备都有独立的开关密码,分别用数字1、2和3表示。即输入1时,空调关闭,输入2时,台灯关闭,输入3时,电视机关闭,当输入为4时,表示要关闭所有设备。请你使用外观模式编写程序来描述电源总开关的操作。
输入描述
第一行是一个整数 N(1 <= N <= 100),表示后面有 N 行输入。
接下来的 N 行,每行包含一个数字,表示对应设备的开关操作(1表示关闭空调,2表示关闭台灯,3表示关闭电视机,4表示关闭所有设备)。
输出描述
输出关闭所有设备后的状态,当输入的数字不在1-4范围内时,输出Invalid device code.
输入示例
4
1
2
3
4输出示例
Air Conditioner is turned off.
Desk Lamp is turned off.
Television is turned off.
All devices are off.题解
// 设备类
class AirConditioner {
turnOff() {
console.log('Air Conditioner is turned off.');
}
}
class DeskLamp {
turnOff() {
console.log('Desk Lamp is turned off.');
}
}
class Television {
turnOff() {
console.log('Television is turned off.');
}
}
// 电源总开关类(外观类)
class PowerSwitch {
constructor() {
this.devices = {
1: new AirConditioner(),
2: new DeskLamp(),
3: new Television()
};
}
turnOffDevice(code) {
if (code === 4) {
this.turnOffAllDevices();
} else if (this.devices[code]) {
this.devices[code].turnOff();
} else {
console.log('Invalid device code.');
}
}
turnOffAllDevices() {
// for (let device of Object.values(this.devices)) {
// device.turnOff();
// }
console.log('All devices are off.');
}
}
const readline = require('readline')
const r1 = readline.createInterface({
input: process.stdin,
output: process.stdout
})
let userLine
const userInput = []
r1.on('line', (line) => {
if (!userLine) {
userLine = parseInt(line)
} else {
userInput.push(parseInt(line))
}
})
r1.on('close', () => {
main()
process.exit(0)
})
function main() {
const mySwitch = new PowerSwitch()
userInput.forEach(type => {
if (type === 4) {
mySwitch.turnOffAllDevices()
} else {
mySwitch.turnOffDevice(type)
}
})
}【设计模式专题之桥接模式】10-万能遥控器
时间限制:1.000S 空间限制:256MB
题目描述
小明家有一个万能遥控器,能够支持多个品牌的电视。每个电视可以执行开机、关机和切换频道的操作,请你使用桥接模式模拟这个操作。
输入描述
第一行是一个整数 N(1 <= N <= 100),表示后面有 N 行输入。
接下来的 N 行,每行包含两个数字。第一个数字表示创建某个品牌的遥控和电视,第二个数字表示执行的操作。
其中,0 表示创建 Sony 品牌的电视,1 表示创建 TCL 品牌的遥控和电视;
2 表示开启电视、3表示关闭电视,4表示切换频道。
输出描述
对于每个操作,输出相应的执行结果。
输入示例
6
0 2
1 2
0 4
0 3
1 4
1 3输出示例
Sony TV is ON
TCL TV is ON
Switching Sony TV channel
Sony TV is OFF
Switching TCL TV channel
TCL TV is OFF题解
const readline = require('readline')
const r1 = readline.createInterface({
input: process.stdin,
output: process.stdout
})
let userLine
const userInput = []
r1.on('line', (line) => {
if (!userLine) {
userLine = +line
} else {
userInput.push(line)
}
})
r1.on('close', () => {
main()
process.exit(0)
})
function main() {
userInput.forEach(item => {
const [num1, num2] = item.split(' ').map(Number)
if (num1 === 0) {
var tv = new TV('Sony')
} else {
var tv = new TV('TCL')
}
if (num2 === 2) {
tv.powerOn()
} else if (num2 === 3) {
tv.powerOff()
} else if (num2 === 4) {
tv.switchChannel()
}
})
}
class TV {
constructor(brand) {
this.brand = brand;
}
powerOn() {
console.log(`${this.brand} TV is ON`);
}
powerOff() {
console.log(`${this.brand} TV is OFF`);
}
switchChannel() {
console.log(`Switching ${this.brand} TV channel`);
}
}
// 遥控器类
class RemoteControl {
constructor(tv) {
this.tv = tv;
}
powerOn() {
this.tv.powerOn();
}
powerOff() {
this.tv.powerOff();
}
switchChannel() {
this.tv.switchChannel();
}
}【设计模式专题之组合模式】11-公司组织架构
时间限制:1.000S 空间限制:256MB
题目描述
小明所在的公司内部有多个部门,每个部门下可能有不同的子部门或者员工。
请你设计一个组合模式来管理这些部门和员工,实现对公司组织结构的统一操作。部门和员工都具有一个通用的接口,可以获取他们的名称以及展示公司组织结构。
输入描述
第一行是一个整数 N(1 <= N <= 100),表示后面有 N 行输入。
接下来的 N 行,每行描述一个部门或员工的信息。部门的信息格式为 D 部门名称,员工的信息格式为 E 员工名称,其中 D 或 E 表示部门或员工。
输出描述
输出公司的组织结构,展示每个部门下的子部门和员工
输入示例
MyCompany
8
D HR
E HRManager
D Finance
E AccountantA
E AccountantB
D IT
E DeveloperA
E DeveloperB输出示例
Company Structure:
MyCompany
HR
HRManager
Finance
AccountantA
AccountantB
IT
DeveloperA
DeveloperB题解
const readline = require('readline')
const r1 = readline.createInterface({
input: process.stdin,
output: process.stdout
})
let userLine
const userInput = []
let companyName
r1.on('line', (line) => {
if (!companyName) {
companyName = line
} else if (!userLine) {
userLine = +line
} else {
userInput.push(line)
}
})
r1.on('close', () => {
main()
process.exit(0)
})
function main() {
const company = new Company(companyName)
let department
userInput.forEach(line => {
const [type, name] = line.split(' ')
if (type === 'D') {
department = new Department(name)
company.add(department)
} else {
const employee = new Employee(name)
department.add(employee)
}
})
// da log
company.show()
}
class Company {
constructor(name) {
this.name = name
this.children = []
}
add(child) {
this.children.push(child)
}
remove(child) {
this.children.pop(child)
}
show() {
console.log('Company Structure:')
console.log(this.name)
for(let i = 0, child, children = this.children; child = children[i++];) {
child.show()
}
}
}
class Department {
constructor(name) {
this.name = name
this.children = []
}
add(child) {
this.children.push(child)
}
remove(child) {
this.children.pop(child)
}
show() {
console.log(' ' + this.name)
for(let i = 0, child, children = this.children; child = children[i++];) {
child.show()
}
}
}
class Employee {
constructor(name) {
this.name = name
}
show() {
console.log(' ' + this.name)
}
}【设计模式专题之享元模式】12-图形编辑器
时间限制:1.000S 空间限制:256MB
题目描述
在一个图形编辑器中,用户可以绘制不同类型的图形,包括圆形(CIRCLE)、矩形(RECTANGLE)、三角形(TRIANGLE)等。现在,请你实现一个图形绘制程序,要求能够共享相同类型的图形对象,以减少内存占用。
输入描述
输入包含多行,每行表示一个绘制命令。每个命令包括两部分:
图形类型(Circle、Rectangle 或 Triangle)
绘制的坐标位置(两个整数,分别表示 x 和 y)
输出描述
对于每个绘制命令,输出相应图形被绘制的位置信息。如果图形是首次绘制,输出 "drawn at",否则输出 "shared at"。
输入示例
CIRCLE 10 20
RECTANGLE 30 40
CIRCLE 15 25
TRIANGLE 5 15
CIRCLE 10 20
RECTANGLE 30 40输出示例
CIRCLE drawn at (10, 20)
RECTANGLE drawn at (30, 40)
CIRCLE shared at (15, 25)
TRIANGLE drawn at (5, 15)
CIRCLE shared at (10, 20)
RECTANGLE shared at (30, 40)题解
const readline = require('readline')
const r1 = readline.createInterface({
input: process.stdin,
output: process.stdout
})
let userLine
const userInput = []
let companyName
r1.on('line', (line) => {
userInput.push(line)
})
r1.on('close', () => {
main()
process.exit(0)
})
function main() {
const blockFactory = new BlockFactory()
userInput.forEach(line => {
const [type, x, y] = line.split(' ')
blockFactory.createBlock(type, x, y)
})
}
class BlockFactory {
constructor() {
this.shapes = {}
}
createBlock(type, x, y) {
const key = `${type}`
if (!this.shapes[key]) {
this.shapes[key] = new Shape(type, x, y)
console.log(`${type} drawn at (${x}, ${y})`)
return this.shapes[key]
} else {
console.log(`${type} shared at (${x}, ${y})`)
return this.shapes[key]
}
}
}
class Shape {
constructor(type, x, y) {
this.type = type
this.x = x
this.y = y
}
}【设计模式专题之观察者模式】13. 时间观察者
时间限制:1.000S 空间限制:256MB
题目描述
小明所在的学校有一个时钟(主题),每到整点时,它就会通知所有的学生(观察者)当前的时间,请你使用观察者模式实现这个时钟通知系统。
注意点:时间从 0 开始,并每隔一个小时更新一次。
输入描述
输入的第一行是一个整数 N(1 ≤ N ≤ 20),表示学生的数量。
接下来的 N 行,每行包含一个字符串,表示学生的姓名。
最后一行是一个整数,表示时钟更新的次数。
输出描述
对于每一次时钟更新,输出每个学生的姓名和当前的时间。
输入示例
2
Alice
Bob
3输出示例
Alice 1
Bob 1
Alice 2
Bob 2
Alice 3
Bob 3提示信息
初始时钟时间为0(12:00 AM)。
第一次更新后,时钟变为1(1:00 AM),然后通知每个学生,输出学生名称和时钟点数。
第二次更新后,时钟变为2(2:00 AM),然后再次通知每个学生,输出学生名称和时钟点数
第三次更新后,时钟变为3(3:00 AM),然后再次通知每个学生,输出学生名称和时钟点数。
题解
const readline = require('readline')
const r1 = readline.createInterface({
input: process.stdin,
output: process.stdout
})
let userLine
const userInput = []
let repeatTime
r1.on('line', (line) => {
if (!userLine) {
userLine = +line
} else if (line.length > 1) {
userInput.push(line)
} else {
repeatTime = +line
}
})
r1.on('close', () => {
main()
process.exit(0)
})
function main() {
const clock = new Clock()
// 添加观察
userInput.forEach(stu => clock.addObservers(new Student(stu)))
// 通知
while(repeatTime--) {
clock.updateTime()
}
}
class Obsever {
update(time) {
}
}
class Student extends Obsever {
constructor(name) {
super()
this.name = name
}
update(time) {
console.log(`${this.name} ${time}`)
}
}
class Clock {
constructor() {
this.time = 0
this.observers = []
}
notifyObservers() {
this.observers.forEach(ob => ob.update(this.time))
}
updateTime() {
this.time++
this.notifyObservers()
}
addObservers(ob) {
this.observers.push(ob)
}
}【设计模式专题之策略模式】14. 超市打折
时间限制:1.000S 空间限制:256MB
题目描述
小明家的超市推出了不同的购物优惠策略,你可以根据自己的需求选择不同的优惠方式。其中,有两种主要的优惠策略:
\1. 九折优惠策略:原价的90%。
\2. 满减优惠策略:购物满一定金额时,可以享受相应的减免优惠。
具体的满减规则如下:
满100元减5元
满150元减15元
满200元减25元
满300元减40元
请你设计一个购物优惠系统,用户输入商品的原价和选择的优惠策略编号,系统输出计算后的价格。
输入描述
输入的第一行是一个整数 N(1 ≤ N ≤ 20),表示需要计算优惠的次数。
接下来的 N 行,每行输入两个整数,第一个整数M( 0 < M < 400) 表示商品的价格, 第二个整数表示优惠策略,1表示九折优惠策略,2表示满减优惠策略
输出描述
每行输出一个数字,表示优惠后商品的价格
输入示例
4
100 1
200 2
300 1
300 2输出示例
90
175
270
260题解
const readline = require('readline')
const r1 = readline.createInterface({
input: process.stdin,
output: process.stdout
})
let userLine
const userInput = []
r1.on('line', (line) => {
if (!userLine) {
userLine = +line
} else {
userInput.push(line)
}
})
r1.on('close', () => {
main()
process.exit(0)
})
function main() {
userInput.forEach(line => {
const [price, type] = line.split(' ').map(Number)
if (type === 1) {
const strategy = new NineDiscountStrategy()
console.log(strategy.cal(price))
} else {
const strategy = new FullDiscountStrategy()
console.log(strategy.cal(price))
}
})
}
class DiscountStrategy {
cal(origalPrice) {
throw new Error('未实现')
}
}
class NineDiscountStrategy extends DiscountStrategy {
cal(origalPrice) {
return origalPrice * 0.9
}
}
class FullDiscountStrategy extends DiscountStrategy {
constructor() {
super()
this.discountRules = [
{origin: 300, discount: 40},
{origin: 200, discount: 25},
{origin: 150, discount: 15},
{origin: 100, discount: 5},
]
}
cal(originPrice) {
return originPrice - this.discountRules.find(item => originPrice >= item.origin).discount || 0
}
}【设计模式专题之命令模式】15-自助点餐机
时间限制:1.000S 空间限制:256MB
题目描述
小明去奶茶店买奶茶,他可以通过在自助点餐机上来点不同的饮品,请你使用命令模式设计一个程序,模拟这个自助点餐系统的功能。
输入描述
- 第一行是一个整数 n(1 ≤ n ≤ 100),表示点单的数量。
- 接下来的 n 行,每行包含一个字符串,表示点餐的饮品名称。
输出描述
输出执行完所有点单后的制作情况,每行输出一种饮品的制作情况。如果制作完成,输出 "XXX is ready!",其中 XXX 表示饮品名称。
输入示例
4
MilkTea
Coffee
Cola
MilkTea输出示例
MilkTea is ready!
Coffee is ready!
Cola is ready!
MilkTea is ready!题解
const readline = require('readline')
const r1 = readline.createInterface({
input: process.stdin,
output: process.stdout
})
let userLine
const userInput = []
r1.on('line', (line) => {
if (!userLine) {
userLine = +line
} else {
userInput.push(line)
}
})
r1.on('close', () => {
main()
process.exit(0)
})
function main() {
// push Command
const reciver = new Reciver()
userInput.forEach(command => {
const drinkCommand = new DrinkCommand(command)
reciver.add(drinkCommand)
})
reciver.excuteCommand()
}
class Command {
excute() {
throw new Error('未实现')
}
}
class DrinkCommand extends Command{
constructor(drink) {
super()
this.drink = drink
}
excute() {
console.log(`${this.drink} is ready!`)
}
}
class Reciver {
constructor() {
this.commands = []
}
add(command) {
this.commands.push(command)
}
excuteCommand() {
this.commands.forEach(command => command.excute())
}
}【设计模式专题之中介者模式】16-简易聊天室
时间限制:1.000S 空间限制:256MB
题目描述
小明正在设计一个简单的多人聊天室系统,有多个用户和一个聊天室中介者,用户通过中介者进行聊天,请你帮他完成这个系统的设计。
输入描述
第一行包括一个整数N,表示用户的数量(1 <= N <= 100) 第二行是N个用户,比如User1 User2 User3,用空格分隔 第三行开始,每行包含两个字符串,表示消息的发出者和消息内容,用空格分隔
输出描述
对于每个用户,输出一行,包含该用户收到的所有消息内容。
输入示例
3
User1 User2 User3
User1 Hello_All!
User2 Hi_User1!
User3 How_is_everyone?输出示例
User2 received: Hello_All!
User3 received: Hello_All!
User1 received: Hi_User1!
User3 received: Hi_User1!
User1 received: How_is_everyone?
User2 received: How_is_everyone?题解
const readline = require('readline')
const r1 = readline.createInterface({
input: process.stdin,
output: process.stdout
})
let userLine
const userInput = []
let userListOri
r1.on('line', (line) => {
if (!userLine) {
userLine = +line
} else if(!userListOri) userListOri = line
else {
userInput.push(line)
}
})
r1.on('close', () => {
main()
process.exit(0)
})
function main() {
const mediator = new Mediator()
const userList = userListOri.split(' ').map(item => new User(item, mediator))
userList.forEach(item => mediator.resiger(item))
userInput.forEach(item => {
const [us, msg] = item.split(' ')
mediator.sendMessage(us, msg)
})
}
class User {
constructor(username, mediator){
this.username = username
this.mediator = mediator
}
receiveMessage(message) {
console.log(`${this.username} received: ${message}`)
}
}
class Mediator {
constructor() {
this.userList = []
}
resiger(user) {
this.userList.push(user)
}
sendMessage(username, message) {
this.userList.forEach(user => {
if (user.username !== username) {
user.receiveMessage(message)
}
})
}
}【设计模式专题之备忘录模式】17-redo计数器应用
时间限制:1.000S 空间限制:256MB
题目描述
小明正在设计一个简单的计数器应用,支持增加(Increment)和减少(Decrement)操作,以及撤销(Undo)和重做(Redo)操作,请你使用备忘录模式帮他实现。
输入描述
输入包含若干行,每行包含一个字符串,表示计数器应用的操作,操作包括 "Increment"、"Decrement"、"Undo" 和 "Redo"。
输出描述
对于每个 "Increment" 和 "Decrement" 操作,输出当前计数器的值,计数器数值从0开始 对于每个 "Undo" 操作,输出撤销后的计数器值。 对于每个 "Redo" 操作,输出重做后的计数器值。
输入示例
Increment
Increment
Decrement
Undo
Redo
Increment输出示例
1
2
1
2
1
2题解
const readline = require('readline')
const r1 = readline.createInterface({
input: process.stdin,
output: process.stdout
})
let userLine
const userInput = []
let userListOri
r1.on('line', (line) => {
userInput.push(line)
})
r1.on('close', () => {
main()
process.exit(0)
})
function main() {
const editor = new Editor()
userInput.forEach(item => {
if (item === 'Increment') {
editor.add()
} else if (item === 'Decrement') {
editor.decrement()
} else if(item === 'Undo') {
editor.undo()
} else {
editor.redo()
}
})
}
class Memo {
constructor(content) {
this.content = content
}
}
class Editor {
constructor() {
this.memo = [];
this.currentIndex = -1;
this.content = 0;
}
add() {
this.content++;
const memo = new Memo(this.content);
this.memo.splice(this.currentIndex + 1, this.memo.length - this.currentIndex - 1, memo);
this.currentIndex++;
console.log(this.content);
}
decrement() {
this.content--;
const memo = new Memo(this.content);
this.memo.splice(this.currentIndex + 1, this.memo.length - this.currentIndex - 1, memo);
this.currentIndex++;
console.log(this.content);
}
undo() {
if (this.currentIndex > 0) {
this.currentIndex--;
this.content = this.memo[this.currentIndex].content;
console.log(this.content);
} else {
console.log('Nothing to undo.');
}
}
redo() {
if (this.currentIndex < this.memo.length - 1) {
this.currentIndex++;
this.content = this.memo[this.currentIndex].content;
console.log(this.content);
} else {
console.log('Nothing to redo.');
}
}
}【设计模式专题之模板方法模式】18-咖啡馆
时间限制:1.000S 空间限制:256MB
题目描述
小明喜欢品尝不同类型的咖啡,她发现每种咖啡的制作过程有一些相同的步骤,他决定设计一个简单的咖啡制作系统,使用模板方法模式定义咖啡的制作过程。系统支持两种咖啡类型:美式咖啡(American Coffee)和拿铁(Latte)。
咖啡制作过程包括以下步骤:
\1. 研磨咖啡豆 Grinding coffee beans
\2. 冲泡咖啡 Brewing coffee
\3. 添加调料 Adding condiments
其中,美式咖啡和拿铁的调料添加方式略有不同, 拿铁在添加调料时需要添加牛奶Adding milk
输入描述
多行输入,每行包含一个数字,表示咖啡的选择(1 表示美式咖啡,2 表示拿铁)。
输出描述
根据每行输入,输出制作咖啡的过程,包括咖啡类型和各个制作步骤,末尾有一个空行。
输入示例
1
2输出示例
Making American Coffee:
Grinding coffee beans
Brewing coffee
Adding condiments
Making Latte:
Grinding coffee beans
Brewing coffee
Adding milk
Adding condiments题解
const readline = require('readline')
const r1 = readline.createInterface({
input: process.stdin,
output: process.stdout
})
let userLine
const userInput = []
let userListOri
r1.on('line', (line) => {
userInput.push(+line)
})
r1.on('close', () => {
main()
process.exit(0)
})
function main() {
userInput.forEach(type => {
if (type === 1) {
new Coffee().render()
} else {
new Latte().render()
}
})
}
class Template {
render(component) {
this.renderBeforeStart()
this.renderStart();
this.renderComponent(component);
this.renderEnd();
}
renderBeforeStart() {
throw new Error('未实现')
}
renderStart() {
console.log('Grinding coffee beans')
console.log('Brewing coffee')
}
renderComponent() {
throw new Error('未实现')
}
renderEnd() {
console.log('Adding condiments\n')
}
}
class Coffee extends Template {
renderComponent() {
// do nothing
}
renderBeforeStart() {
console.log('Making American Coffee:')
}
}
class Latte extends Template {
renderComponent() {
console.log('Adding milk')
}
renderBeforeStart() {
console.log('Making Latte:')
}
}【设计模式专题之迭代器模式】19-学生名单
题目描述
小明是一位老师,在进行班级点名时,希望有一个学生名单系统,请你实现迭代器模式提供一个迭代器使得可以按顺序遍历学生列表。
输入描述
第一行是一个整数 N (1 <= N <= 100), 表示学生的数量。
接下来的 N 行,每行包含一个学生的信息,格式为 姓名 学号
输出描述
输出班级点名的结果,即按顺序遍历学生列表,输出学生的姓名和学号
输入示例
3
Alice 1001
Bob 1002
Charlie 1003输出示例
Alice 1001
Bob 1002
Charlie 1003题解
const readline = require('readline')
const r1 = readline.createInterface({
input: process.stdin,
output: process.stdout
})
let userLine
const userInput = []
let userListOri
r1.on('line', (line) => {
if (!userLine) {
userLine = +line
} else {
userInput.push(line)
}
})
r1.on('close', () => {
main()
process.exit(0)
})
function main() {
const studentList = new StudentList()
userInput.forEach(item => {
const [name, id] = item.split(' ')
studentList.addStudent({name, id})
})
const iterator = studentList.createIterator()
while(!iterator.isDone()) {
const student = iterator.getCurrentStudent()
console.log(`${student.name} ${student.id}`)
iterator.next()
}
}
class StudentIterator {
constructor(studentList) {
this.index = 0;
this.students = studentList
}
first() {
this.index = 0
}
next() {
this.index++
}
isDone() {
return this.index >= this.students.length
}
getCurrentStudent() {
if (this.index < this.students.length) {
return this.students[this.index]
} else {
return null
}
}
}
class StudentList {
constructor() {
this.students = []
}
addStudent(student) {
this.students.push(student)
}
createIterator() {
return new StudentIterator(this.students)
}
}【设计模式专题之状态模式】20-开关台灯
题目描述
小明家有一个灯泡,刚开始为关闭状态(OffState)。台灯可以接收一系列的指令,包括打开("ON")、关闭("OFF")和闪烁("blink")。每次接收到一个指令后,台灯会执行相应的操作,并输出当前灯泡的状态。请设计一个程序模拟这个灯泡系统。
输入描述
第一行是一个整数 n(1 <= n <= 1000),表示接收的命令数量。
接下来的 n 行,每行包含一个字符串 s,表示一个命令("ON"、"OFF"或"blink")。
输出描述
对于每个命令,输出一行,表示执行该命令后灯泡的状态。
输入示例
5
ON
OFF
BLINK
OFF
ON输出示例
Light is ON
Light is OFF
Light is Blinking
Light is OFF
Light is ON题解
const readline = require('readline')
const r1 = readline.createInterface({
input: process.stdin,
output: process.stdout
})
let userLine
const userInput = []
let userListOri
r1.on('line', (line) => {
if (!userLine) {
userLine = +line
} else {
userInput.push(line)
}
})
r1.on('close', () => {
main()
process.exit(0)
})
function main() {
const light = new Light()
userInput.forEach(item => {
switch (item) {
case 'ON':
light.setState(new OnState())
break;
case 'OFF':
light.setState(new OffState())
break;
case 'BLINK':
light.setState(new BlinkState())
break;
default:
// code
}
light.render()
})
}
class State {
render(context) {
throw new Error('未实现具体状态')
}
}
class OnState extends State {
render(context) {
console.log('Light is ON')
}
}
class OffState extends State {
render(context) {
console.log('Light is OFF')
}
}
class BlinkState extends State {
render(context) {
console.log('Light is Blinking')
}
}
class Light {
constructor() {
this.state = new OffState()
}
setState(state) {
this.state = state
}
render() {
this.state.render(this)
}
}【设计模式专题之责任链模式】21-请假审批
题目描述
小明所在的公司请假需要在OA系统上发布申请,整个请求流程包括多个处理者,每个处理者负责处理不同范围的请假天数,如果一个处理者不能处理请求,就会将请求传递给下一个处理者,请你实现责任链模式,可以根据请求天数找到对应的处理者。
审批责任链由主管(Supervisor), 经理(Manager)和董事(Director)组成,他们分别能够处理3天、7天和10天的请假天数。如果超过10天,则进行否决。
输入描述
第一行是一个整数N(1 <= N <= 100), 表示请求申请的数量。
接下来的N行,每行包括一个请求申请的信息,格式为"姓名 请假天数"
输出描述
对于每个请假请求,输出一行,表示该请求是否被批准。如果被批准/否决,输出被哪一个职级的人批准/否决。
输入示例
4
Alice 2
Bob 5
Tom 10
Jerry 12输出示例
Alice Approved by Supervisor.
Bob Approved by Manager.
Tom Approved by Director.
Jerry Denied by Director.题解
const readline = require('readline')
const r1 = readline.createInterface({
input: process.stdin,
output: process.stdout
})
let userLine
const userInput = []
let userListOri
r1.on('line', (line) => {
if (!userLine) {
userLine = +line
} else {
userInput.push(line)
}
})
r1.on('close', () => {
main()
process.exit(0)
})
function main() {
const supervisor = new Supervisor()
const manager = new Manager()
const director = new Director()
supervisor.setNext(manager)
manager.setNext(director)
userInput.forEach(item => {
const [name, day] = item.split(' ')
const request = {name, day}
supervisor.handleRequest(request)
})
}
class Handler {
setNext(handler) {
throw new Error('未实现具体处理者')
}
handleRequest(request) {
throw new Error('未实现具体处理请求')
}
}
class Supervisor extends Handler {
setNext(handler) {
this.nextHandler = handler
}
handleRequest(request) {
if (request.day <= 3) {
console.log(`${request.name} Approved by Supervisor.`)
} else if (this.nextHandler) {
this.nextHandler.handleRequest(request)
} else {
console.log(`${request.name} Denied by Supervisor.`)
}
}
}
class Manager extends Handler {
setNext(handler) {
this.nextHandler = handler
}
handleRequest(request) {
if (request.day <= 7) {
console.log(`${request.name} Approved by Manager.`)
} else if (this.nextHandler) {
this.nextHandler.handleRequest(request)
} else {
console.log(`${request.name} Denied by Manager.`)
}
}
}
class Director extends Handler {
setNext(handler) {
this.nextHandler = handler
}
handleRequest(request) {
if (request.day <= 10) {
console.log(`${request.name} Approved by Director.`)
} else if (this.nextHandler) {
this.nextHandler.handleRequest(request)
} else {
console.log(`${request.name} Denied by Director.`)
}
}
}【设计模式专题之解释器模式】22-数学表达式
题目描述
小明正在设计一个计算器,用于解释用户输入的简单数学表达式,每个表达式都是由整数、加法操作符+、乘法操作符组成的,表达式中的元素之间用空格分隔,请你使用解释器模式帮他实现这个系统。
输入描述
每行包含一个数学表达式,表达式中包含整数、加法操作符(+)和乘法操作符(*)。 表达式中的元素之间用空格分隔。
输出描述
对于每个输入的数学表达式,每行输出一个整数,表示对应表达式的计算结果。
输入示例
2 + 3
5 * 2
3 + 4 * 2输出示例
5
10
11题解
const readline = require('readline');
const rl = readline.createInterface({
input: process.stdin,
output: process.stdout
});
class Expression {
interpret() {
throw new Error('interpret method must be implemented');
}
}
class NumberExpression extends Expression {
constructor(value) {
super();
this.value = value;
}
interpret() {
return this.value;
}
}
class OperationExpression extends Expression {
constructor(left, operator, right) {
super();
this.left = left;
this.operator = operator;
this.right = right;
}
interpret() {
let leftValue = this.left.interpret();
let rightValue = this.right.interpret();
switch (this.operator) {
case '+':
return leftValue + rightValue;
case '*':
return leftValue * rightValue;
default:
throw new Error('Unsupported operator');
}
}
}
class ExpressionParser {
constructor(expression) {
this.tokens = expression.split(/\s+/);
this.index = 0;
}
parse() {
return this.parseExpression();
}
parseExpression() {
let expression = this.parseTerm();
while (this.index < this.tokens.length && this.tokens[this.index] === '+') {
this.index++;
expression = new OperationExpression(expression, '+', this.parseTerm());
}
return expression;
}
parseTerm() {
let term = this.parseFactor();
while (this.index < this.tokens.length && this.tokens[this.index] === '*') {
this.index++;
term = new OperationExpression(term, '*', this.parseFactor());
}
return term;
}
parseFactor() {
let token = this.tokens[this.index];
if (isNaN(token)) {
throw new Error('Invalid token');
}
this.index++;
return new NumberExpression(parseInt(token, 10));
}
}
rl.on('line', (line) => {
const parser = new ExpressionParser(line);
const expression = parser.parse();
console.log(expression.interpret());
}).on('close', () => {
console.log('\n'); // 添加换行符,美观输出
process.exit(0);
});【设计模式专题之访问者模式】23-图形的面积
题目描述
小明家有一些圆形和长方形面积的土地,请你帮他实现一个访问者模式,使得可以通过访问者计算每块土地的面积。
图形的面积计算规则如下:
- 圆形的面积计算公式为:3.14 * 半径 * 半径
- 矩形的面积计算公式为:长 * 宽
输入描述
第一行是一个整数 n(1 <= n <= 1000),表示图形的数量。
接下来的 n 行,每行描述一个图形,格式为 "Circle r" 或 "Rectangle width height",其中 r、width、height 是正整数。
输出描述
对于每个图形,输出一行,表示该图形的面积。
输入示例
3
Circle 5
Rectangle 3 4
Circle 2输出示例
78.5
12
12.56题解
const readline = require('readline')
const r1 = readline.createInterface({
input: process.stdin,
output: process.stdout
})
let userLine
const userInput = []
let userListOri
r1.on('line', (line) => {
if (!userLine) {
userLine = +line
} else {
userInput.push(line)
}
})
r1.on('close', () => {
main()
process.exit(0)
})
function main() {
const visitor = new ConcreteVisitor()
userInput.forEach(item => {
const [type, ...arr] = item.split(' ')
if (type === 'Circle') {
const circle = new Circle(arr[0])
circle.accept(visitor)
} else {
const rectangle = new Rectangle(arr[0], arr[1])
rectangle.accept(visitor)
}
})
}
class Shape {
constructor() {
}
accept(visitor) {
throw new Error('未实现同意访问的方法')
}
}
class Circle extends Shape {
constructor(r) {
super()
this.r = r
}
accept(visitor) {
visitor.visitCircle(this)
}
}
class Rectangle extends Shape {
constructor(a, b) {
super()
this.a = a
this.b = b
}
accept(visitor) {
visitor.visitRectangle(this)
}
}
class Visitor {
visitCircle(element) {
throw new Error('未实现访问圆的方法')
}
visitRectangle(element) {
throw new Error('未实现访问矩形的方法')
}
}
class ConcreteVisitor extends Visitor {
visitCircle(element) {
console.log(element.r * element.r * 3.14)
}
visitRectangle(element) {
console.log(element.a * element.b)
}
}参考资料
图解23种设计模式(TypeScript版)——前端切图仔提升内功的必经之路 - 个人文章 - SegmentFault 思否
人工智能 - 前端设计模式:教科书般的实践指南 - 个人文章 - SegmentFault 思否
【设计模式专题之单例模式】1.小明的购物车 (kamacoder.com)
前端的23种设计模式及应用场景_前端常用设计模式-CSDN博客
前端需要了解的9种设计模式 什么是设计模式?设计模式的类型一. 结构型模式(Structural Patterns)二. 创建型模式(Creat-腾讯云开发者社区-腾讯云 (tencent.com)
盘点前端开发中最常见的几种设计模式 - 掘金 (juejin.cn)
