当前位置:  开发笔记 > 编程语言 > 正文

功能编程是否取代了GoF设计模式?

如何解决《功能编程是否取代了GoF设计模式?》经验,为你挑选了15个好方法。

自从我去年开始学习F#和OCaml以来,我已经阅读了大量的文章,这些文章坚持认为设计模式(特别是Java)是命令式语言中缺少的功能的变通方法.我发现的一篇文章提出了相当强烈的主张:

我遇到过的大多数人都读过Gang of Four的"设计模式"一书.任何自尊的程序员都会告诉你,这本书与语言无关,而且无论你使用哪种语言,这些模式都适用于软件工程.这是一个崇高的主张.不幸的是,它与事实相去甚远.

功能语言极具表现力.在函数式语言中,人们不需要设计模式,因为语言可能是如此高级,您最终会编写概念,一起消除设计模式.

函数式编程的主要特性包括作为一等值,currying,不可变值等的函数.对我来说,OO设计模式近似于任何这些特性似乎并不明显.

另外,在支持OOP的函数式语言(例如F#和OCaml)中,对我来说很明显,使用这些语言的程序员将使用与其他OOP语言相同的设计模式.事实上,现在我每天都使用F#和OCaml,我在这些语言中使用的模式与我在Java中编写时使用的模式之间没有明显的差异.

功能编程是否消除了对OOP设计模式的需求,是否有任何理由?如果是这样,您是否可以发布或链接到典型OOP设计模式及其功能等效的示例?



1> jalf..:

你引用的博客文章夸大了它的主张.FP不会消除对设计模式的需求.术语"设计模式"并未广泛用于描述FP语言中的相同内容.但他们存在.函数式语言有很多最佳实践规则,"当遇到问题X时,使用看起来像Y的代码",这基本上就是设计模式.

但是,大多数特定于OOP的设计模式在函数式语言中几乎无关紧要是正确的.

我认为一般来说,设计模式只能用于弥补语言中的缺点,我认为应该特别具有争议性.如果另一种语言可以解决同样的问题,那么其他语言也不需要设计模式.该语言的用户可能甚至不知道问题存在,因为,这不是该语言的问题.

以下是四人帮对此问题的看法:

编程语言的选择很重要,因为它会影响一个人的观点.我们的模式假设具有Smalltalk/C++级语言功能,并且该选择决定了哪些可以轻松实现,哪些不能轻松实现.如果我们假设过程语言,我们可能会包含称为"继承","封装"和"多态"的设计模式.同样,我们的一些模式直接由不太常见的面向对象语言支持.例如,CLOS有多种方法,可以减少对访问者等模式的需求.事实上,Smalltalk和C++之间存在足够的差异意味着某些模式可以在一种语言中比另一种语言更容易表达.(例如,参见Iterator.)

(以上是"设计模式导论"一书中的引用,第4页,第3段)

函数式编程的主要特性包括作为一等值,currying,不可变值等的函数.对我来说,OO设计模式近似于任何这些特性似乎并不明显.

什么是命令模式,如果不是近似的一流函数?:)在FP语言中,您只需将函数作为参数传递给另一个函数.在OOP语言中,您必须将该函数包装在一个类中,您可以实例化该函数,然后将该对象传递给另一个函数.效果是一样的,但在OOP中它被称为设计模式,它需要更多的代码.什么是抽象工厂模式,如果不是currying?将参数一次传递给函数,以配置最终调用它时吐出的值.

所以,有些GoF设计模式在FP语言中变得多余,因为存在更强大和更易于使用的替代方案.

但当然,仍然有设计这些图案不是通过FP语言解决.单身的FP等价物是多少?(暂时忽略单身人士通常使用的可怕模式)

它也有两种方式.正如我所说,FP也有其设计模式,人们通常不会这样认为它们.

但是你可能遇到过monad.如果不是"处理全球国家"的设计模式,它们是什么?这是一个在OOP语言中如此简单的问题,那里不存在等效的设计模式.

我们不需要"增加静态变量"或"从该套接字读取"的设计模式,因为它就是你所做的.

在(纯)函数语言中,副作用和可变状态是不可能的,除非你使用monad"设计模式"或任何允许相同事物的任何其他方法来解决它.

另外,在支持OOP的函数式语言(例如F#和OCaml)中,对我来说很明显,使用这些语言的程序员将使用与其他OOP语言相同的设计模式.事实上,现在我每天都使用F#和OCaml,并且我在这些语言中使用的模式与我在Java中编写时使用的模式之间没有明显的差异.

也许是因为你还在考虑当务之急?很多人在一生中处理命令式语言后,在尝试使用函数式语言时很难放弃这种习惯.(我在F#上看到了一些非常有趣的尝试,其中字面上每个函数只是一串'let'语句,基本上就像你采用了C程序一样,并用'let'替换了所有的分号.:))

但另一种可能性可能是你没有意识到你正在解决一些需要用OOP语言设计模式的问题.

当您使用currying,或将函数作为参数传递给另一个时,请停下来思考如何使用OOP语言执行此操作.

功能编程是否消除了对OOP设计模式的需求,是否有任何理由?

是的.:)当您使用FP语言时,您不再需要特定于OOP的设计模式.但是你仍然需要一些通用的设计模式,比如MVC或其他非OOP特定的东西,你需要一些新的FP特定的"设计模式".所有语言都有它们的缺点,设计模式通常是我们解决它们的方式.

无论如何,你可能会觉得尝试使用"更干净"的FP语言很有意思,比如ML(我个人最喜欢的,至少是出于学习目的),或者Haskell,当你没有OOP拐杖时面对新事物.


正如所料,有些人反对我将设计模式定义为"修补某种语言中的缺点",所以这就是我的理由:正如已经说过的,大多数设计模式都是针对一种编程范式,有时甚至是一种特定语言.通常,它们解决仅存在于该范例中的问题(参见FP的monads或OOP的抽象工厂).为什么FP中不存在抽象工厂模式?因为它试图解决的问题不存在.因此,如果在FP语言中不存在OOP语言中存在问题,那么显然这是OOP语言的缺点.问题可以解决,但是你的语言没有这样做,但是需要一堆样板代码来解决它.理想情况下,我们'所有问题都消失了.任何仍然存在的问题原则上都是语言的缺点.;)


S.Lott:他们描述了以给定语言存在的问题的解决方案,是的.FP语言中没有Command设计模式,因为它试图解决的问题不存在.这意味着他们解决了语言本身无法解决的问题.也就是说,语言的缺点
设计模式描述了基本问题的一般解决方案 但这也是编程语言和平台的作用.因此,当您使用的语言和平台不够时,您可以使用设计模式.
请注意,Haskell中的monad用于除可变状态之外的其他内容,例如异常,延续,列表推导,解析,异步编程等.但是monad的所有这些应用都可能被称为模式.
当然,monad是一个数学概念,但它们也是一种模式.monad的"FP模式"与monad的数学概念有些不同.前者是用于绕过纯FP语言中某些"限制"的模式.后者是一种通用的数学概念.
monad是一个数学概念,你用你的分类来扩展它.当然,您可以将函数,幺半群,monad,矩阵或其他数学概念视为设计模式,但这些更像是算法和数据结构......基本概念,语言无关.
"在(纯)函数语言中,副作用和可变状态是不可能的,除非你使用monad"设计模式"或任何其他允许相同事物的方法来解决它." 我花了很多时间来摧毁这个废话神话.这不愉快.看到它再次出现评估令人沮丧.
jalf:Monad是一个设计模式,如果它是一个类型类,你可以定义实例?如果Monad是一种设计模式,那么数字也应该是Haskell中的设计模式.在OO语言中,你不能只实现"抽象工厂"接口,并期望实现实际上是一个抽象工厂...而且我也看不到Singleton如何适用于函数式语言,没有概念对象那么你如何谈论FP中的单身人士?
@jalf:你还说,"monad的\"FP模式与monad的数学概念有些不同.前者是用于绕过纯FP语言中某些"限制"的模式.后者是通用的数学概念." 这种虚假信息明确地加剧了对您和您的读者的这一概念的可怕误解.
"我认为,一般来说设计模式只能用来修补语言中的缺点,这并不是一个特别有争议的观点."这不是一个有争议的问题.这是假的.设计模式描述问题的解决方案.与语言缺点没有任何关系.
@jalf:在Haskell中,IO类型构造函数*恰好是monad*.它也是:协变函子,尖头函子,应用函子和许多(许多)其他东西.它是一个monad*与标记效果*完全无关,或者,正如你(不恰当地)提到的那样,"允许可变的状态/副作用".
@jalf:换句话说,monad与效果跟踪完全没有任何关系.实际上,我可以想到在每天编程中出现的完全偏离I/O的bazillion monad.这些monad出现在各种编程语言中,包括那些明显偏离FP论文的语言.
@jalf:有几件事是错的.首先,FP语言不允许可变状态和副作用.在所有.诸如Haskell之类的语言使用IO类型构造函数通过包装需要RealWorld参数的值来"标记效果".这些功能明显不具有副作用.你会注意到这里没有使用"monad"这个词.这是一张我明确解决这个问题的幻灯片http://projects.tmorris.net/public/what-does-monad-mean/artifacts/1.1/chunk-html/ar01s02s02s02.html
设计模式如何处理语言中的shorcommings的另一个例子:在java中你需要Observer模式.但是,由于C#在语言中具有正确的事件/侦听器,因此您不需要它.所以这只是两个非常相似的语言的样本,而另一个设计模式仍然无关紧要.
@jalf在副作用的背景下,我会说"纯FP"而不仅仅是"FP".当Tony Morris写出"FP语言不允许可变状态和副作用"之类的东西时,他会比你给出的所有主流FP语言都要混淆了更多的新词,因为Lisp不仅允许甚至需要副作用. ..
@Aidenn:为什么它不是设计模式?因为没有人用标题中的"设计模式"写一本关于它们的书吗?我不明白为什么它比一个单身人士更少的设计模式.
这不是简化.这简直是​​假的.这就是让我困扰的问题,但不会让那些试图学习并被这些陈述误导的学生困扰.需要付出很多努力来支持它们.至少,这是我的教学经验.你最好不要说什么,而不是说伪装成一个有用的简化的虚假陈述(看起来你甚至在这里欺骗了自己).我不怀疑你在帮助方面的诚意,但要注意你很容易意外地做出相反的事情.
我同意monad实际上是一个数学概念,但在纯函数式编程中,它们是用于封装可变状态的设计模式.但是,除非STM是用于管理并发的模式,否则除了之外不想这样做.
S. Lott.以下是关于主题的一些有趣的即兴演奏,设计模式是关于语言缺陷:http://c2.com/cgi/wiki?AreDesignPatternsMissingLanguageFeatures http://norvig.com/design-patterns/我注意到Norvig片段被提及几次以下.这是一篇非常受人尊敬的文章,他认为你提出反对的具体观点.我认为如果没有更深入地参与其论点,你就不能宣称它是假的.
@Tony:你能解释一下,而不是简单地说我错了吗?纯FP语言如何允许可变的状态和副作用呢?
@jalf:其次,像Clean之类的其他语言使用一种称为唯一性类型的技术,它与IO类型构造函数完全不同.
@jalf:你的错误和误导性解释有如此多的评价是非常令人失望的.我希望你理解这一点,而不是亲自接受我的抗议.我知道你的意思很好,但你无意中撤消了很多艰苦的工作.有太多不正确的信息要继续在这里解决所有问题,所以我希望你能体会到我希望传达的东西.
语言就是它们的本质,因为它们呈现出一种特定的范式.OOP语言需要设计模式来解决某些问题并不一定是语言的缺点.相反,语言采用特定范式(即OOP或FP),并试图以尽可能最完整的方式表示该范例,同时仍然足够小以使程序员能够随意携带.建议语言必须涵盖所有问题,这掩盖了一些人可能不希望在您的问题空间中工作的事实(例如,更喜欢游戏编程).
@Tony:如果你实际上*提出了你的观点,那么"深入了解你想要的观点"将会容易得多.到目前为止,我所看到的只是一个关于我是多么被误导的咆哮,以及一种让你的生活变得更加艰难的哀叹.我的观点,正如@Codex很好地总结的那样,FP语言中的monad是一种"设计模式".推出一种代码结构来解决一系列常见问题.
虽然MVC和其他版本仍可用于FP语言,但它们并不完全是设计模式,而是架构模式.设计模式仅影响少数组件,而体系结构模式触及整个应用程序.
奥拉夫克:是的,我同意.我只提到了可变状态,因为这是一个很好的例子,在命令式语言中需要一个"模式"太简单了.但我同意,monad的所有其他用途也将被视为模式.
@jalf:为了避免你对这个简短的解释提出反对意见(如果你有疑问,我很乐意扩展),然后你会意识到你刚刚成为一名学生,对于他来说"难以摆脱严重的误解".换句话说,您可能会深入了解我希望就教育进步所提出的观点.

2> S.Lott..:

功能编程是否消除了对OOP设计模式的需求,是否有任何理由?

函数式编程与面向对象编程不同.面向对象的设计模式不适用于函数式编程.相反,您拥有函数式编程设计模式.

对于函数式编程,您不会阅读OO设计模式书籍,您将阅读有关FP设计模式的其他书籍.

语言不可知论者

不是完全的.只有语言与OO语言无关.设计模式根本不适用于程序语言.它们在关系数据库设计上下文中几乎没有意义.它们在设计电子表格时不适用.

典型的OOP设计模式及其功能等同?

以上不应该存在.这就像要求将一段程序代码重写为OO代码.嗯...如果我将原始的Fortran(或C)翻译成Java,我除了翻译之外没有做任何其他事情.如果我完全将其重写为OO范例,它将不再像原始的Fortran或C那样 - 它将无法识别.

从OO设计到功能设计没有简单的映射.他们看待问题的方式截然不同.

函数式编程(与所有编程风格一样)具有设计模式.关系数据库有设计模式,OO有设计模式,过程编程有设计模式.一切都有设计模式,甚至建筑物的建筑.

设计模式 - 作为一个概念 - 是一种永恒的构建方式,无论技术或问题领域如何.但是,特定的设计模式适用于特定的问题域和技术.

每个想到他们正在做的事情的人都会发现设计模式.


MVC不是OO设计.它的架构设计 - 这种模式适用范围非常广泛.
Java 8将包括闭包,也称为匿名函数,即lambda表达式.这将使Java的命令设计模式过时.这是语言缺点的一个例子,不是吗?他们添加了一个缺失的功能,现在您不需要设计模式.
结束语的+1.设计模式旨在简化编程,使得生成的程序更有效,符合它们的目的.

3> 小智..:

布莱恩关于语言和模式之间紧密联系的评论是关键所在,

这个讨论的缺失部分是习语的概念.Coplien的书"高级C++"在这里产生了巨大的影响.早在他发现克里斯托弗亚历山大和没有名字专栏之前(你也不能在不读亚历山大的情况下明智地谈论模式),他谈到了掌握成语在真正学习语言中的重要性.他以C中的字符串副本为例,(*from ++ =*to ++); 您可以将其视为缺失语言特征(或库特征)的绑定,但真正重要的是它比任何部分都更重要的思想或表达单位.

这就是模式和语言试图做的事情,让我们更简洁地表达我们的意图.思想单元越丰富,你表达的思想就越复杂.拥有丰富的,共享的词汇表,从一系列的规模 - 从系统架构到钻头 - 让我们能够进行更加智能的对话,以及对我们应该做什么的想法.

作为个人,我们也可以学习.这是练习的全部内容.我们每个人都能理解和使用我们永远无法想到的事情.语言,框架,图书馆,模式,习语等都在分享知识财富方面占有一席之地.


谢谢!*这*是关于降低认知负担的"概念性组合"的模式.

4> bright..:

GOF书明确地与OOP联系在一起 - 标题是设计模式 - 可重用面向对象软件的元素(强调我的).



5> Darius Bacon..:

动态编程中的设计模式由Peter Norvig对这个一般主题进行了深思熟虑,虽然关于"动态"语言而不是"功能性"(有重叠).



6> folone..:

这是另一个链接,讨论这个主题:http://blog.ezyang.com/2010/05/design-patterns-in-haskel/

在他的博客文章中,爱德华用Haskell描述了所有23种原始GoF模式.


这篇文章似乎并没有真正展示Haskell中的设计模式,而是展示了Haskell如何在没有所述模式的情况下满足这些需求.
@Fresheyball:取决于你对模式的定义.是否将列表上的函数映射为访问者模式的变体?我一般认为答案是肯定的.模式应该超越特定的语法.应用的函数可以包装为对象或作为函数指针传递,但对我来说概念是相同的.你不同意吗?

7> Brian..:

当你试图在"设计模式"(一般)和"FP与OOP"的水平上看这个时,你会发现的答案充其量是模糊的.

但是,在两个轴上更深入,并考虑特定的设计模式特定的语言功能,事情变得更加清晰.

因此,例如,当使用具有代数数据类型和模式匹配,闭包,第一类函数等的语言时,某些特定模式(如访问者,策略,命令观察者)肯定会改变或消失.来自GoF书籍的其他一些模式仍然但是,"坚持下去".

总的来说,我会说,随着时间的推移,特定的模式正在被新的(或刚刚升级的)语言特征所消除.这是语言设计的自然过程; 随着语言变得更高级,以前只能在书中使用示例调用的抽象现在成为特定语言特征或库的应用程序.

(旁白:这是我最近写的一篇博客,其中有关于FP和设计模式的更多讨论的其他链接.)


是的,但是从_pattern_改变了,这是一个你在书中阅读并适用于你的代码的设计理念,只是"只是代码".也就是说,"使用联合类型和模式匹配"就是你通常用这种语言编写代码的方式.(打个比方:如果没有语言有'for`循环而且它们都只有`while`循环,那么"For"可能是一个迭代模式.但是当`for`只是语言支持的结构以及人们如何正常编码时,然后它不是一个模式 - 你不需要一个模式,它只是代码,男人.)
换句话说,对于"这是一种模式"而言,一个也许并不差的试金石是:现在的代码以这种方式写给一个二年级的CS专业本科生,用一年的语言编程经验.如果你向他们展示代码,他们就会"那是一个聪明的设计",那么这就是一种模式.如果你向他们展示代码,他们会"好吧,呃!",那么这不是一种模式.(如果你向任何已经完成ML/F#/ Haskell一年的人展示这个"访客",他们会"好吧,呃!")

8> 小智..:

Norvig的演讲暗示了他们对所有GoF模式的分析,并且他们说23种模式中的16种在函数式语言中具有更简单的实现,或者仅仅是语言的一部分.因此,大概至少有七个是a)同样复杂或b)不存在于语言中.不幸的是,我们没有列举它们!

我认为很明显,GoF中的大多数"创造性"或"结构"模式只是让Java或C++中的原始类型系统做你想做的事情的技巧.但无论你编写什么语言,其余的都值得考虑.

一个可能是Prototype; 虽然它是JavaScript的基本概念,但它必须在其他语言中从头开始实现.

我最喜欢的模式之一是Null Object模式:表示没有东西作为一个对象,没有任何东西.这可能更容易用功能语言建模.然而,真正的成就是观点的转变.


@Marcin根据我的经验,OO程序员通常指的是子类型多态(通常只使用Object),利用强制转换来实现它,或ad-hoc多态(重载等).当函数式程序员说多态时,它们意味着参数多态(即适用于_any_类型的数据--Int,函数,列表),这可能更像是OO的泛型编程,而不像OO程序员通常称之为多态.
由于GoF模式是专门为基于类的OOP语言设计的,因此做了多么奇怪的分析.看起来有点像分析管钳是否适合做电气工作.

9> Anders Rune ..:

我会说当你有一个像Lisp这样支持宏的语言时,你可以构建自己的特定于域的抽象,抽象通常比一般的习语解决方案好得多.


您可以在没有宏的情况下构建特定于域的抽象(甚至是嵌入式抽象).宏只是通过添加自定义语法让你了解它们.
您可以将Lisp视为一组用于构建编程语言的乐高积木 - 它是一种语言,但它也是一种元语言.这意味着对于任何问题域,您可以自定义设计一种没有任何明显缺陷的语言.这将需要一些练习,而KurtGödel可能不同意,但是值得花一些时间与Lisp一起看看它带来了什么(提示,宏).

10> 小智..:

甚至OO设计模式解决方案也是特定于语言的.设计模式是解决您的编程语言无法解决的常见问题的解决方案.在Java中,Singleton模式解决了一个(简化)问题.在Scala中,除了Class之外,还有一个名为Object的顶级构造.这是懒惰的实例化,只有一个.您不必使用Singleton模式来获取Singleton.这是语言的一部分.



11> Germán..:

正如其他人所说,存在功能编程特有的模式.我认为摆脱设计模式的问题不是转换到功能性问题,而是语言功能问题.

看看Scala如何消除"单例模式":你只需要声明一个对象而不是一个类.另一个功能,模式匹配,有助于避免访客模式的笨拙.请参阅此处的比较:http: //andymaleh.blogspot.com/2008/04/scalas-pattern-matching-visitor-pattern.html

与F#一样,Scala是OO功能的融合.我不知道F#但它可能有这种功能.

闭包以函数语言存在,但不必限于它们.他们帮助委托模式.

再观察一次.这段代码实现了一个模式:它是如此经典,它是如此的元素,我们通常不认为它是一个"模式",但它肯定是:

for(int i = 0; i < myList.size(); i++) { doWhatever(myList.get(i)); }

像Java和C#这样的命令式语言采用了本质上是一个功能构造来处理这个问题:"foreach".



12> Edwin Buck..:

模式是解决一次又一次看到的类似问题的方法,然后进行描述和记录.所以不,FP不会取代模式; 然而,FP可能会创建新的模式,并使一些当前的"最佳实践"模式"过时".


GoP模式是解决特定类型的编程语言限制问题的方法.例如"我想在类上间接,并告诉他们创建对象" - >"你不能,但你可以创建类似元类的对象,称为工厂"."我想要多次发送" - >"你不能,但你可以实现称为访客模式的迷宫".等等.如果你没有具有特定限制的OOP语言,那么这些模式都没有意义.

13> Kaz..:

GoF设计模式是针对作为Simula 67后代的OO语言的编码解决方案,如Java和C++.

设计模式处理的大多数"弊病"都是由以下因素引起的:

静态类型的类,它们指定对象但不是它们自己的对象;

对单个调度的限制(只有最左边的参数用于选择一个方法,其余的参数仅被视为静态类型:如果它们具有动态类型,则由特殊方法对其进行排序);

常规函数调用和面向对象函数调用之间的区别,意味着面向对象的函数不能作为期望常规函数的函数参数传递,反之亦然; 和

区分"基本类型"和"类类型".

这些设计模式中没有一个不会在Common Lisp对象系统中消失,即使解决方案的结构与相应的设计模式基本相同.(此外,该对象系统在GoF书籍之前已有十多年了.在该书首次出版的同一年,Common Lisp成为ANSI标准.)

就函数式编程而言,模式是否适用于它取决于给定的函数式编程语言是否具有某种对象系统,以及它是否在受益于模式的对象系统之后建模.这种类型的面向对象与函数式编程不能很好地混合,因为状态的突变位于前端和中心.

构造和非变异访问与函数式编程兼容,因此可以应用与抽象访问或构造有关的模式:工厂,外观,代理,装饰器,访问者等模式.

另一方面,状态和策略等行为模式可能不会直接应用于功能性OOP,因为状态的突变是其核心.这并不意味着他们不适用; 也许他们以某种方式结合任何可用于模拟可变状态的技巧.


"GoF设计模式是编码解决方案的食谱"只是一个错误的陈述.

14> sclv..:

我想插一些Jeremy Gibbons的优秀但有些密集的论文:"设计模式作为高阶数据类型 - 通用程序"和"Iterator模式的本质"(两者都可以在这里找到:http:// www. comlab.ox.ac.uk/jeremy.gibbons/publications/).

这两者都描述了惯用功能结构如何覆盖其他(面向对象)设置中特定设计模式所覆盖的地形.



15> 小智..:

如果不打开类型系统,就无法进行此讨论.

函数式编程的主要特性包括作为一等值,currying,不可变值等的函数.对我来说,OO设计模式近似于任何这些特性似乎并不明显.

那是因为这些功能没有解决OOP所做的相同问题......它们是命令式编程的替代方案.对OOP的FP答案在于ML和Haskell的类型系统......特别是和类型,抽象数据类型,ML模块,Haskell类型类.

但当然仍然存在FP语言无法解决的设计模式.单身的FP等价物是多少?(暂时忽略单身人士通常使用的可怕模式)

类型组首先做的是消除对单身人士的需求.

您可以查看23列表并消除更多,但我现在没有时间.


类型类(OOP接口的FP等价物)如何消除对单例的需求(FP等效于全局状态)?
推荐阅读
重庆制造漫画社
这个屌丝很懒,什么也没留下!
DevBox开发工具箱 | 专业的在线开发工具网站    京公网安备 11010802040832号  |  京ICP备19059560号-6
Copyright © 1998 - 2020 DevBox.CN. All Rights Reserved devBox.cn 开发工具箱 版权所有