Java SPI机制与应用
SPI全称Service Provider Interface, 是Java提供的一套用来被第三方实现或者扩展的接口实际上是"基于接口的编程+策略模式+配置文件"组合实现的动态加载机制, 更是设计模式的生动体现它可以用来启用框架扩展和替换组件. SPI的作用就是为这些被扩展的API寻找服务实现我们一般推荐模块之间基于接口编程, 模块之间不对实现类进行硬编码, 一旦代码里涉及具体的实现类, 就违反了可拔插的原则, 如果需要替换一种实现, 就需要修改代码简单demopublic interface UploadFile {
void upload(String file);
}
public class AliyunCDN implements UploadFile {
@Override
public void upload(String url) {
System.out.println("upload to AliyunCDN");
}
}
public class QiniuCDN implements UploadFile {
@Override
public void upload(String url) {
System.out.println("upload to QiniuCDN");
}
}然后需要在resources目录下新建META-INF/services目录, 并且在这个目录下新建一个与上述接口的全限定名一致的文件, 在这个文件中写入接口的实现类的全限定名org.test.AliyunCDN
org.test.QiniuCDN启动类public class Test {
public static void main(String[] args) {
ServiceLoader<UploadFile> uploadFiles = ServiceLoader.load(UploadFile.class);
for (UploadFile u : uploadFiles) {
u.upload("filePath");
}
}
}项目结构C:
├─pom.xml
└─src
└─main
├─java
│ └─org
│ └─test
│ AliyunCDN.java
│ QiniuCDN.java
│ Test.java
│ UploadFile.java
└─resources
└─META-INF
└─services
org.test.UploadFile运行 upload to AliyunCDN
upload to QiniuCDN使用场景Spring Spring中大量使用了SPI, 比如: 对Servlet3.0规范对ServletContainerInitializer的实现(Tomcat加载SpringBoot项目的War包)/自动类型转换Type Conversion SPI(Converter SPI/Formatter SPI)等SpringBoot SpringBoot中spring.factories和SpringFactoriesLoaderHutool拼音工具-PinyinUtil 官方文档和其它门面模块类似,采用SPI方式识别所用的库。例如你想用Pinyin4j,只需引入jar,Hutool即可自动识别。Dubbo Dubbo中也大量使用SPI的方式实现框架的扩展, 不过它对Java提供的原生SPI做了封装,允许用户扩展实现Filter接口JDBC JDBC4.0以后不再需要Class.forName即可注册驱动原创声明,本文系作者授权腾讯云开发者社区发表,未经许可,不得转载。
【面经分享】-一年工作经验阿里三面
最近原来实习时候的Boss联系我,说他跳槽到了阿里,问我有没有兴趣面一个Java后台开发岗位。考虑到我只工作了一年,现在去阿里肯定要降薪,因此也没有太强烈的意愿。但出于提升自我的角度考虑,参加了面试。一面(电话面试一小时)首先做一个简单的自我介绍,主要包括学校经历和工作经历。我工作经历只有一年,大部分时间都是在做产品设计和UI/UX Design,因此隔着电话都能感受到面试官的shock。● Java基础。自动拆装箱如何实现,String,StringBuffer,StringBuilder的异同以及各自的实现。● JVM基础。JVM的内存模型,常见的垃圾回收算法。● 事务ACID,编程时如何保证事务,分布式情况下如何保证事务。● 由于分布式相关场景我没有接触过,因此面试官一直诱导我去设计实现一个分布式事务。● 数据库乐观锁和悲观锁。如何实现一个乐观锁。● 消息队列使用场景,Kafka的架构以及原理。● 什么是restful api,和rpc调用有什么区别。● 单例的几种写法。volatile关键字有什么作用。以上就是电话面试的大体问题,面试完之后,又发给我三道算法题目,要求我一小时内完成,下面是三道算法题:● 翻转一个long类型数字。例如输入123456L,输出654321L。- Leetcode翻转integer的变种。考察能否正确处理溢出的情况。● 输入一个double,要求返回与它最接近的.49或.99的数字。例如12.77返回12.99,11.02返回10.99,12.61返回12.49。● 有三个线程ABC分别向一个数组中写入a,l,i,要求最终的写入结果形如alialiali...写入次数由A线程决定。这三道题目做的还比较顺利,第二天面试官又联系我阐述一下第一题和第三题的思路,然后通知我可以参加下一轮了。二面(电话面试一小时)二面主要考察了一些开放式的问题。● 首先还是自我介绍。主要是工作后的经历。介绍一下工作一年所在team的产品,我承担了什么职责。● 开放式问题。如何设计一个rpc框架。● 开放式问题。如何设计一个服务注册中心。● 集合类源码。HashMap是如何实现的,扩容的过程,为什么要扩容为2倍。HashMap中的链表替换为数组可以吗?时间复杂度相同吗?● 集合类源码。线程安全的HashMap是什么?(HashTable和ConcurrentHashMap)ConcurrentHashMap是如何实现的?(Java7分段锁和Java8的CAS+Lock)和HashTable相比有什么优势?● 红黑树的结构,时间复杂度是多少,如何计算的● 什么是CAS操作,如何实现一个自定义锁● 数据库设计。有一张很大的order表,如何设计能够提升查询效率(同时满足根据买家id和卖家id查询)?二面也同样是一小时左右,面试过程还算顺利。只是当时我在厦门鼓浪屿的一家小餐馆吃晚饭,周围的嘈杂和闷热使我很烦躁,感觉面试官态度有些傲慢……ps.一面二面结束后面试官都各种暗示我要疯狂加班能不能接受blabla……三面(电话面试一个半小时)二面结束后的第三天,就收到了现场三面的通知。然而我还在厦门旅行,因此改为了电话面试。三面是一个大Boss,因此面试的问题都更考察一些分析问题的能力。● 介绍一下你工作一年学习到什么?所在项目的架构是什么样的?UI/UX设计有哪些规范(由于我说我学到了一些UI/UX设计方法,因此面试官就问了)?● 数据隔离级别,脏读幻读。● 线程池原理。● Synchronized的实现,锁的升级过程。● K8s的作用,K8s的底层架构。● 对我业余时间做的一些项目做了介绍。● 你觉得加入阿里你能给阿里带来什么?● 进入阿里你需要忍受很多困难,需要迎难而上,如果绩效考评拿到差评,你会怎么办?三面总的来说也还算顺利,面试官也算和蔼。总结整个流程从一面到三面结束大约持续了10天左右。总的来说,问题都是预期范围内的,虽然面试过程中问到了一些分布式相关问题,我都没有任何经验,这时候不要放弃,主动说出你的思路,然后在面试官的诱导下,相信你能说出属于的答案。最后,是我总结的一些面试Java后台工程师必须要掌握的知识点。集合类源码● ArrayList:内部数据结构,数组扩容机制 ● LinkedList:内部数据结构,为什么使用双向链表 ● HashMap:内部数据结构,put方法的完整流程,扩容机制 ● LikedHashMap:内部数据结构,如何实现一个Cache ● TreeMap:内部数据结构,时间复杂度 ● CurrentHashMap:内部数据结构,Java7分段锁,Java8 CAS+SynchronizedJava基础● 自动拆装箱原理● String,StringBuffer和StringBuilder● Throwable● reader和stream● NIOJVM基础● JVM内存模型● 常见垃圾回收算法并发编程基础● Synchronized关键字原理● wait,notify,sleep● 安全的终止线程以及线程的状态转换● 自定义Lock● 线程池原理数据库基础● 数据库三范式,事务ACID,隔离级别,视图,索引● JPA实体状态● EntityManger网络基础● TCP/IP常见设计模式● 装饰者,模板方法,策略,工厂,状态
Python装饰器
装饰器的优点是能够抽离出大量函数中与函数功能本身无关的雷同代码并继续重用。即,可以将函数“修饰”为完全不同的行为,可以有效的将业务逻辑正交分解。概括的讲,装饰器的作用就是为已经存在的对象添加额外的功能。例如记录日志,需要对某些函数进行记录。笨的办法,每个函数加入代码,如果代码变了,就悲催了。装饰器的办法,定义一个专门日志记录的装饰器,对需要的函数进行装饰。Python 的 Decorator在使用上和Java/C#的Annotation很相似,都是在方法名前面加一个@XXX注解来为这个方法装饰一些东西。但是,Java/C#的Annotation也很让人望而却步,在使用它之前你需要了解一堆Annotation的类库文档,让人感觉就是在学另外一门语言。而Python使用了一种相对于Decorator Pattern和Annotation来说非常优雅的方法,这种方法不需要你去掌握什么复杂的OO模型或是Annotation的各种类库规定,完全就是语言层面的玩法:一种函数式编程的技巧。装饰器背后的原理在Python中,装饰器实现是十分方便。原因是:函数可以被扔来扔去。Python的函数就是对象要理解装饰器,就必须先知道,在Python里,函数也是对象(functions are objects)。明白这一点非常重要,让我们通过一个例子来看看为什么。def shout(word="yes"):
**return** word.capitalize() + "!"
print(shout())
\# outputs : 'Yes!'
\# 作为一个对象,你可以像其他对象一样把函数赋值给其他变量
scream = shout
\# 注意我们没有用括号:我们不是在调用函数,
\# 而是把函数'shout'的值绑定到'scream'这个变量上
\# 这也意味着你可以通过'scream'这个变量来调用'shout'函数
print(scream())
\# outputs : 'Yes!'
\# 不仅如此,这也还意味着你可以把原来的名字'shout'删掉,
\# 而这个函数仍然可以通过'scream'来访问
del shout
**try**:
print(shout())
**except** NameError as e:
print(e)
\# outputs: "name 'shout' is not defined"
print(scream())
\# outputs: 'Yes!'Python 函数的另一个有趣的特性是,它们可以在另一个函数体内定义。def talk():
\# 你可以在 'talk' 里动态的(on the fly)定义一个函数...
**def** whisper(word="yes"):
**return** word.lower() + "..."
\# ... 然后马上调用它!
print(whisper())
\# 每当调用'talk',都会定义一次'whisper',然后'whisper'在'talk'里被调用
talk()
\# outputs:
\# "yes..."
\# 但是"whisper" 在 "talk"外并不存在:
**try**:
print(whisper())
**except** NameError as e:
print(e)
\# outputs : "name 'whisper' is not defined"函数引用(Functions references)你刚刚已经知道了,Python的函数也是对象,因此:可以被赋值给变量可以在另一个函数体内定义那么,这样就意味着一个函数可以返回另一个函数:def get_talk(type="shout"):
\# 我们先动态定义一些函数
**def** shout(word="yes"):
**return** word.capitalize() + "!"
**def** whisper(word="yes"):
**return** word.lower() + "..."
\# 然后返回其中一个
**if** type == "shout":
\# 注意:我们是在返回函数对象,而不是调用函数,所以不要用到括号 "()"
**return** shout
**else**:
**return** whisper
\# 那你改如何使用d呢?
\# 先把函数赋值给一个变量
talk = get_talk()
\# 你可以发现 "talk" 其实是一个函数对象:
print(talk)
\# outputs : <function shout at 0xb7ea817c>
\# 这个对象就是 get_talk 函数返回的:
print(talk())
\# outputs : Yes!
\# 你甚至还可以直接这样使用:
print(get_talk("whisper")())
\# outputs : yes...既然可以返回一个函数,那么也就可以像参数一样传递:def shout(word="yes"):
**return** word.capitalize() + "!"
scream = shout
**def** do_something_before(func):
print("I do something before then I call the function you gave me")
print(func())
do_something_before(scream)
\# outputs:
\# I do something before then I call the function you gave me
\# Yes!装饰器实战现在已经具备了理解装饰器的所有基础知识了。装饰器也就是一种包装材料,它们可以让你在执行被装饰的函数之前或之后执行其他代码,而且不需要修改函数本身。手工制作的装饰器\# 一个装饰器是一个需要另一个函数作为参数的函数
**def** my_shiny_new_decorator(a_function_to_decorate):
\# 在装饰器内部动态定义一个函数:wrapper(原意:包装纸).
\# 这个函数将被包装在原始函数的四周
\# 因此就可以在原始函数之前和之后执行一些代码.
**def** the_wrapper_around_the_original_function():
\# 把想要在调用原始函数前运行的代码放这里
print("Before the function runs")
\# 调用原始函数(需要带括号)
a_function_to_decorate()
\# 把想要在调用原始函数后运行的代码放这里
print("After the function runs")
\# 直到现在,"a_function_to_decorate"还没有执行过 (HAS NEVER BEEN EXECUTED).
\# 我们把刚刚创建的 wrapper 函数返回.
\# wrapper 函数包含了这个函数,还有一些需要提前后之后执行的代码,
\# 可以直接使用了(It's ready to use!)
**return** the_wrapper_around_the_original_function
\# Now imagine you create a function you don't want to ever touch again.
**def** a_stand_alone_function():
print("I am a stand alone function, don't you dare modify me")
a_stand_alone_function()
\# outputs: I am a stand alone function, don't you dare modify me
\# 现在,你可以装饰一下来修改它的行为.
\# 只要简单的把它传递给装饰器,后者能用任何你想要的代码动态的包装
\# 而且返回一个可以直接使用的新函数:
a_stand_alone_function_decorated = my_shiny_new_decorator(a_stand_alone_function)
a_stand_alone_function_decorated()
\# outputs:
\# Before the function runs
\# I am a stand alone function, don't you dare modify me
\# After the function runs装饰器的语法糖我们用装饰器的语法来重写一下前面的例子:\# 一个装饰器是一个需要另一个函数作为参数的函数
**def** my_shiny_new_decorator(a_function_to_decorate):
\# 在装饰器内部动态定义一个函数:wrapper(原意:包装纸).
\# 这个函数将被包装在原始函数的四周
\# 因此就可以在原始函数之前和之后执行一些代码.
**def** the_wrapper_around_the_original_function():
\# 把想要在调用原始函数前运行的代码放这里
print("Before the function runs")
\# 调用原始函数(需要带括号)
a_function_to_decorate()
\# 把想要在调用原始函数后运行的代码放这里
print("After the function runs")
\# 直到现在,"a_function_to_decorate"还没有执行过 (HAS NEVER BEEN EXECUTED).
\# 我们把刚刚创建的 wrapper 函数返回.
\# wrapper 函数包含了这个函数,还有一些需要提前后之后执行的代码,
\# 可以直接使用了(It's ready to use!)
**return** the_wrapper_around_the_original_function
@my_shiny_new_decorator
**def** another_stand_alone_function():
print("Leave me alone")
another_stand_alone_function()
\# outputs:
\# Before the function runs
\# Leave me alone
\# After the function runs是的,这就完了,就这么简单。@decorator 只是下面这条语句的简写(shortcut):another_stand_alone_function = my_shiny_new_decorator(another_stand_alone_function)装饰器语法糖其实就是装饰器模式的一个Python化的变体。为了方便开发,Python已经内置了好几种经典的设计模式,比如迭代器(iterators)。当然,你还可以堆积使用装饰器:def bread(func):
**def** wrapper():
print("</''''''\>")
func()
print("<\______/>")
**return** wrapper
**def** ingredients(func):
**def** wrapper():
print("#tomatoes#")
func()
print("~salad~")
**return** wrapper
**def** sandwich(food="--ham--"):
print(food)
sandwich()
\# outputs: --ham--
sandwich = bread(ingredients(sandwich))
sandwich()
\# outputs:
\# </''''''\>
\# #tomatoes#
\# --ham--
\# ~salad~
\# <\______/>用Python的装饰器语法表示:def bread(func):
**def** wrapper():
print("</''''''\>")
func()
print("<\______/>")
**return** wrapper
**def** ingredients(func):
**def** wrapper():
print("#tomatoes#")
func()
print("~salad~")
**return** wrapper
@bread
@ingredients
**def** sandwich(food="--ham--"):
print(food)
sandwich()
\# outputs:
\# </''''''\>
\# #tomatoes#
\# --ham--
\# ~salad~
\# <\______/>装饰器放置的顺序也很重要:def bread(func):
**def** wrapper():
print("</''''''\>")
func()
print("<\______/>")
**return** wrapper
**def** ingredients(func):
**def** wrapper():
print("#tomatoes#")
func()
print("~salad~")
**return** wrapper
@ingredients
@bread
**def** strange_sandwich(food="--ham--"):
print(food)
strange_sandwich()
\# outputs:
\##tomatoes#
\# </''''''\>
\# --ham--
\# <\______/>
\# ~salad~给装饰器函数传参\# 这不是什么黑色魔法(black magic),你只是必须让wrapper传递参数:
**def** a_decorator_passing_arguments(function_to_decorate):
**def** a_wrapper_accepting_arguments(arg1, arg2):
print("I got args! Look:", arg1, arg2)
function_to_decorate(arg1, arg2)
**return** a_wrapper_accepting_arguments
\# 当你调用装饰器返回的函数式,你就在调用wrapper,而给wrapper的
\# 参数传递将会让它把参数传递给要装饰的函数
@a_decorator_passing_arguments
**def** print_full_name(first_name, last_name):
print("My name is", first_name, last_name)
print_full_name("Peter", "Venkman")
\# outputs:
\# I got args! Look: Peter Venkman
\# My name is Peter Venkman含参数的装饰器在上面的装饰器调用中,比如@decorator,该装饰器默认它后面的函数是唯一的参数。装饰器的语法允许我们调用decorator时,提供其它参数,比如@decorator(a)。这样,就为装饰器的编写和使用提供了更大的灵活性。\# a new wrapper layer
**def** pre_str(pre=''):
\# old decorator
**def** decorator(F):
**def** new_F(a, b):
print(pre + " input", a, b)
**return** F(a, b)
**return** new_F
**return** decorator
\# get square sum
@pre_str('^_^')
**def** square_sum(a, b):
**return** a ** 2 + b ** 2
\# get square diff
@pre_str('T_T')
**def** square_diff(a, b):
**return** a ** 2 - b ** 2
print(square_sum(3, 4))
print(square_diff(3, 4))
\# outputs:
\# ('^_^ input', 3, 4)
\# 25
\# ('T_T input', 3, 4)
\# -7上面的pre_str是允许参数的装饰器。它实际上是对原有装饰器的一个函数封装,并返回一个装饰器。我们可以将它理解为一个含有环境参量的闭包。当我们使用@pre_str(‘^_^’)调用的时候,Python能够发现这一层的封装,并把参数传递到装饰器的环境中。该调用相当于:square_sum = pre_str('^_^') (square_sum)装饰“类中的方法”Python的一个伟大之处在于:方法和函数几乎是一样的(methods and functions are really the same),除了方法的第一个参数应该是当前对象的引用(也就是 self)。这也就意味着只要记住把 self 考虑在内,你就可以用同样的方法给方法创建装饰器:def method_friendly_decorator(method_to_decorate):
**def** wrapper(self, lie):
lie = lie - 3 # very friendly, decrease age even more :-)
**return** method_to_decorate(self, lie)
**return** wrapper
**class** Lucy(object):
**def** __init__(self):
self.age = 32
@method_friendly_decorator
**def** say_your_age(self, lie):
print("I am %s, what did you think?" % (self.age + lie))
l = Lucy()
l.say_your_age(-3)
\# outputs: I am 26, what did you think?当然,如果你想编写一个非常通用的装饰器,可以用来装饰任意函数和方法,你就可以无视具体参数了,直接使用 *args, **kwargs 就行:def a_decorator_passing_arbitrary_arguments(function_to_decorate):
\# The wrapper accepts any arguments
**def** a_wrapper_accepting_arbitrary_arguments(*args, **kwargs):
print("Do I have args?:")
print(args)
print(kwargs)
\# Then you unpack the arguments, here *args, **kwargs
\# If you are not familiar with unpacking, check:
\# http://www.saltycrane.com/blog/2008/01/how-to-use-args-and-kwargs-in-python/
function_to_decorate(*args, **kwargs)
**return** a_wrapper_accepting_arbitrary_arguments
@a_decorator_passing_arbitrary_arguments
**def** function_with_no_argument():
print("Python is cool, no argument here.")
function_with_no_argument()
\# outputs
\# Do I have args?:
\# ()
\# {}
\# Python is cool, no argument here.
@a_decorator_passing_arbitrary_arguments
**def** function_with_arguments(a, b, c):
print(a, b, c)
function_with_arguments(1, 2, 3)
\# outputs
\# Do I have args?:
\# (1, 2, 3)
\# {}
\# 1 2 3
@a_decorator_passing_arbitrary_arguments
**def** function_with_named_arguments(a, b, c, platypus="Why not ?"):
print("Do %s, %s and %s like platypus? %s" % (a, b, c, platypus))
function_with_named_arguments("Bill", "Linus", "Steve", platypus="Indeed!")
\# outputs
\# Do I have args ? :
\# ('Bill', 'Linus', 'Steve')
\# {'platypus': 'Indeed!'}
\# Do Bill, Linus and Steve like platypus? Indeed!
**class** Mary(object):
**def** __init__(self):
self.age = 31
@a_decorator_passing_arbitrary_arguments
**def** say_your_age(self, lie=-3): # You can now add a default value
print("I am %s, what did you think ?" % (self.age + lie))
m = Mary()
m.say_your_age()
\# outputs
\# Do I have args?:
\# (<__main__.Mary object at 0xb7d303ac>,)
\# {}
\# I am 28, what did you think?装饰类在上面的例子中,装饰器接收一个函数,并返回一个函数,从而起到加工函数的效果。在Python 2.6以后,装饰器被拓展到类。一个装饰器可以接收一个类,并返回一个类,从而起到加工类的效果。def decorator(aClass):
**class** newClass:
**def** __init__(self, age):
self.total_display = 0
self.wrapped = aClass(age)
**def** display(self):
self.total_display += 1
print("total display", self.total_display)
self.wrapped.display()
**return** newClass
@decorator
**class** Bird:
**def** __init__(self, age):
self.age = age
**def** display(self):
print("My age is", self.age)
eagleLord = Bird(5)
**for** i **in** range(3):
eagleLord.display()在decorator中,我们返回了一个新类newClass。在新类中,我们记录了原来类生成的对象(self.wrapped),并附加了新的属性total_display,用于记录调用display的次数。我们也同时更改了display方法。通过修改,我们的Bird类可以显示调用display的次数了。内置装饰器Python中有三种我们经常会用到的装饰器, property、 staticmethod、 classmethod,他们有个共同点,都是作用于类方法之上。property 装饰器property 装饰器用于类中的函数,使得我们可以像访问属性一样来获取一个函数的返回值。class XiaoMing:
first_name = '明'
last_name = '小'
@property
**def** full_name(self):
**return** self.last_name + self.first_name
xiaoming = XiaoMing()
print(xiaoming.full_name)例子中我们像获取属性一样获取 full_name 方法的返回值,这就是用 property 装饰器的意义,既能像属性一样获取值,又可以在获取值的时候做一些操作。staticmethod 装饰器staticmethod 装饰器同样是用于类中的方法,这表示这个方法将会是一个静态方法,意味着该方法可以直接被调用无需实例化,但同样意味着它没有 self 参数,也无法访问实例化后的对象。class XiaoMing:
@staticmethod
**def** say_hello():
print('同学你好')
XiaoMing.say_hello()
\# 实例化调用也是同样的效果
\# 有点多此一举
xiaoming = XiaoMing()
xiaoming.say_hello()classmethod 装饰器classmethod 依旧是用于类中的方法,这表示这个方法将会是一个类方法,意味着该方法可以直接被调用无需实例化,但同样意味着它没有 self 参数,也无法访问实例化后的对象。相对于 staticmethod 的区别在于它会接收一个指向类本身的 cls 参数。class XiaoMing:
name = '小明'
@classmethod
**def** say_hello(cls):
print('同学你好, 我是' + cls.name)
print(cls)
XiaoMing.say_hello()wraps 装饰器一个函数不止有他的执行语句,还有着 name(函数名),doc (说明文档)等属性,我们之前的例子会导致这些属性改变。def decorator(func):
**def** wrapper(*args, **kwargs):
"""doc of wrapper"""
print('123')
**return** func(*args, **kwargs)
**return** wrapper
@decorator
**def** say_hello():
"""doc of say hello"""
print('同学你好')
print(say_hello.__name__)
print(say_hello.__doc__)由于装饰器返回了 wrapper 函数替换掉了之前的 say_hello 函数,导致函数名,帮助文档变成了 wrapper 函数的了。解决这一问题的办法是通过 functools 模块下的 wraps 装饰器。from functools import wraps
**def** decorator(func):
@wraps(func)
**def** wrapper(*args, **kwargs):
"""doc of wrapper"""
print('123')
**return** func(*args, **kwargs)
**return** wrapper
@decorator
**def** say_hello():
"""doc of say hello"""
print('同学你好')
print(say_hello.__name__)
print(say_hello.__doc__)装饰器总结装饰器的核心作用是name binding。这种语法是Python多编程范式的又一个体现。大部分Python用户都不怎么需要定义装饰器,但有可能会使用装饰器。鉴于装饰器在Python项目中的广泛使用,了解这一语法是非常有益的。常见错误:“装饰器”=“装饰器模式”设计模式是一个在计算机世界里鼎鼎大名的词。假如你是一名 Java 程序员,而你一点设计模式都不懂,那么我打赌你找工作的面试过程一定会度过的相当艰难。但写 Python 时,我们极少谈起“设计模式”。虽然 Python 也是一门支持面向对象的编程语言,但它的鸭子类型设计以及出色的动态特性决定了,大部分设计模式对我们来说并不是必需品。所以,很多 Python 程序员在工作很长一段时间后,可能并没有真正应用过几种设计模式。不过装饰器模式(Decorator Pattern)是个例外。因为 Python 的“装饰器”和“装饰器模式”有着一模一样的名字,我不止一次听到有人把它们俩当成一回事,认为使用“装饰器”就是在实践“装饰器模式”。但事实上,它们是两个完全不同的东西。“装饰器模式”是一个完全基于“面向对象”衍生出的编程手法。它拥有几个关键组成:一个统一的接口定义、若干个遵循该接口的类、类与类之间一层一层的包装。最终由它们共同形成一种“装饰”的效果。而 Python 里的“装饰器”和“面向对象”没有任何直接联系,**它完全可以只是发生在函数和函数间的把戏。事实上,“装饰器”并没有提供某种无法替代的功能,它仅仅就是一颗“语法糖”而已。下面这段使用了装饰器的代码:@log_time
@cache_result
**def** foo(): pass基本完全等同于:def foo(): pass
foo = log_time(cache_result(foo))装饰器最大的功劳,在于让我们在某些特定场景时,可以写出更符合直觉、易于阅读的代码。它只是一颗“糖”,并不是某个面向对象领域的复杂编程模式。参考链接:Primer on Python Decorators[Decorator Basics:Python’s functions are objects]
dlvm-netcore 开源框架后台管理
dlvm-netcore 开源框架程序开发人员都想找到一个快速开发框架,网络上的框架也有很多。一般都是静态页面的居多并没有后台功能及代码,有些有代码的也十分简单或复杂不能很容易的扩展,想找到一个完美的权限分配功能并能和您创建的功能模块及操作按钮对应的框架就更难了。如果你想找到一个自带完美权限管理,不用写后台代码。并想只用拖拉和前台 js+Sql 代码结合实能实现业务功能创建和复杂的逻辑关系的框架;框架的开发语言是 C#, 要求跨平台,要求开源,支持多数据库,就好好学习下 DLVM 开发平台吧,以下简称 DM 平台。我们做为一个有多年开发经验的开发团队,经过网上开源框架的对比后把多个开源框架的优势和特殊功能集中进行优中选优,进行了二次开发后,形成了我们团队的开发平台(DM 平台)。DM 平台介绍DLVM 是一个集数据库、逻辑、视图及模型为一体的并涵盖了常用基础套件,以 NetCore 为主的底层框架。具备安全性、可扩展性、可配置性及可视化操作等优点,并且具有一键创建模块的功能。DM 平台发布以来已被广大爱好者用到了企业、政府、医疗、金融、互联网等各个领域中,架构精良、易于扩展、可配置性强、操作可视化的设计模式、工匠精神打磨每一个细节,深入开发者的内心,并荣获开源中国《最受欢迎中国开源软件》奖杯,期间也帮助了不少软件公司在短期内完成项目的开发并交付使用,客户反响良好并快速得到回报。DM 平台是作者和自己的开发团队结合了多年开发经验,以及各方面的应用案例,把多个开源框架的优势和特殊功能集中进行优中选优,进行了二次开发后,完成了一次全部重构,也纳入很多新的思想。不管是从开发者模式、底层架构、逻辑处理还是到用户界面,用户交互体验上都有了与众不同、独竖一帜的表现。努力为大中小微企业打造全方位企业级快速开发解决方案。DM 平台优势零代码开发是指开发简单功能模块时可一键生成无须代码,如果开发复杂模块只需学习 DM 平台的插件功能,书写 JavaScript 代码调用相关 Api 函数即可实现,对程序初学者可快速入门并参与项目开发。在表单设计、视图设计、审批流程等方面 DM 平台采用可视化操作,所见即所得;平台采用 NetCore 框架开发,支持前后端分离,具有可运行在国外 \ 国内系统的跨平台性;平台支持多语言、多币种,可以后台直接配置即可;DM 平台可以开发多个子系统并独立运行,每个子系统的各功能模块可快速生成并具备增、删、改、查、审批流、相关报表等基本功能,开发人员可以在此功能基础上添加自己的业务逻辑完成项目的开发。完美的权限分配功能,可以把您开发的各功能模块直接进行权限分配及角色化分。子系统生产示意图官网及手册官方网站:???http://www.dlvm.vip???操作手册:???http://www.dlvm.vip/index.php?act=zaixianwd??平台演示图
React系列十 - 高阶组件以及组件补充
一. 高阶组件1.1. 认识高阶组件什么是高阶组件呢?相信很多同学都听说过,也用过 高阶函数,它们非常相似,所以我们可以先来回顾一下什么是 高阶函数。高阶函数的维基百科定义:至少满足以下条件之一:接受一个或多个函数作为输入;输出一个函数;JavaScript中比较常见的filter、map、reduce都是高阶函数。那么说明是高阶组件呢?高阶组件的英文是 Higher-Order Components,简称为 HOC;官方的定义:高阶组件是参数为组件,返回值为新组件的函数;我们可以进行如下的解析:首先, 高阶组件 本身不是一个组件,而是一个函数;其次,这个函数的参数是一个组件,返回值也是一个组件;高阶组件的调用过程类似于这样:const EnhancedComponent = higherOrderComponent(WrappedComponent);高阶函数的编写过程类似于这样:function higherOrderComponent(WrapperComponent) {
return class NewComponent extends PureComponent {
render() {
return <WrapperComponent/>
}
}
}在ES6中,类表达式中类名是可以省略的,所以可以可以写成下面的写法:function higherOrderComponent(WrapperComponent) {
return class extends PureComponent {
render() {
return <WrapperComponent/>
}
}
}另外,组件的名称都可以通过displayName来修改:? 修改名称完整的代码,我们可以这样来编写:import React, { PureComponent } from 'react';
function higherOrderComponent(WrapperComponent) {
return class NewComponent extends PureComponent {
render() {
return <WrapperComponent/>
}
}
}
class App extends PureComponent {
render() {
return (
<div>
App
</div>
)
}
}
export default higherOrderComponent(App);高阶组件并不是React API的一部分,它是基于React的组合特性而形成的设计模式;高阶组件在一些React第三方库中非常常见:比如redux中的connect;(后续会讲到)比如react-router中的withRouter;(后续会讲到)在我们的开发中,高阶组件可以帮助我们做哪些事情呢?1.2. 高阶组件的使用1.2.1. props的增强不修改原有代码的情况下,添加新的props加入我们有如下案例:class Header extends PureComponent {
render() {
const { name, age } = this.props;
return <h2>Header {name + age}</h2>
}
}
export default class App extends PureComponent {
render() {
return (
<div>
<Header name="aaa" age={18} />
</div>
)
}
}我们可以通过一个高阶组件,让使用者在不破坏原有结构的情况下对某个组件增强props:function enhanceProps(WrapperCpn, otherProps) {
return props => <WrapperCpn {...props} {...otherProps} />
}
const EnhanceHeader = enhanceProps(Header, {height: 1.88})利用高阶组件来共享Context利用高阶组件来共享Context属性import React, { PureComponent, createContext } from 'react';
const UserContext = createContext({
nickname: "默认",
level: -1
})
function Header(props) {
return (
<UserContext.Consumer>
{
value => {
const { nickname, level } = value;
return <h2>Header {"昵称:" + nickname + "等级" + level}</h2>
}
}
</UserContext.Consumer>
)
}
function Footer(props) {
return (
<UserContext.Consumer>
{
value => {
const { nickname, level } = value;
return <h2>Footer {"昵称:" + nickname + "等级" + level}</h2>
}
}
</UserContext.Consumer>
)
}
const EnhanceHeader = enhanceProps(Header, { height: 1.88 })
export default class App extends PureComponent {
render() {
return (
<div>
<UserContext.Provider value={{ nickname: "why", level: 90 }}>
<Header />
<Footer />
</UserContext.Provider>
</div>
)
}
}利用高阶组件withUser:import React, { PureComponent, createContext } from 'react';
const UserContext = createContext({
nickname: "默认",
level: -1
})
function withUser(WrapperCpn) {
return props => {
return (
<UserContext.Consumer>
{
value => {
return <WrapperCpn {...props} {...value}/>
}
}
</UserContext.Consumer>
)
}
}
function Header(props) {
const { nickname, level } = props;
return <h2>Header {"昵称:" + nickname + "等级:" + level}</h2>
}
function Footer(props) {
const { nickname, level } = props;
return <h2>Footer {"昵称:" + nickname + "等级:" + level}</h2>
}
const UserHeader = withUser(Header);
const UserFooter = withUser(Footer);
export default class App extends PureComponent {
render() {
return (
<div>
<UserContext.Provider value={{ nickname: "why", level: 90 }}>
<UserHeader />
<UserFooter />
</UserContext.Provider>
</div>
)
}
}1.2.2. 渲染判断鉴权在开发中,我们可能遇到这样的场景:某些页面是必须用户登录成功才能进行进入;如果用户没有登录成功,那么直接跳转到登录页面;这个时候,我们就可以使用高阶组件来完成鉴权操作:function LoginPage() {
return <h2>LoginPage</h2>
}
function CartPage() {
return <h2>CartPage</h2>
}
export default class App extends PureComponent {
render() {
return (
<div>
<CartPage/>
</div>
)
}
}编写鉴权的高阶组件:function loginAuth(Page) {
return props => {
if (props.isLogin) {
return <Page/>
} else {
return <LoginPage/>
}
}
}完整的代码如下:import React, { PureComponent } from 'react';
function loginAuth(Page) {
return props => {
if (props.isLogin) {
return <Page/>
} else {
return <LoginPage/>
}
}
}
function LoginPage() {
return <h2>LoginPage</h2>
}
function CartPage() {
return <h2>CartPage</h2>
}
const AuthCartPage = loginAuth(CartPage);
export default class App extends PureComponent {
render() {
return (
<div>
<AuthCartPage isLogin={true}/>
</div>
)
}
}1.2.3. 生命周期劫持import React, { PureComponent } from 'react';
class Home extends PureComponent {
UNSAFE_componentWillMount() {
this.begin = Date.now();
}
componentDidMount() {
this.end = Date.now();
const interval = this.end - this.begin;
console.log(`Home渲染使用时间:${interval}`)
}
render() {
return (
<div>
<h2>Home</h2>
<p>我是home的元素,哈哈哈</p>
</div>
)
}
}
class Detail extends PureComponent {
UNSAFE_componentWillMount() {
this.begin = Date.now();
}
componentDidMount() {
this.end = Date.now();
const interval = this.end - this.begin;
console.log(`Detail渲染使用时间:${interval}`)
}
render() {
return (
<div>
<h2>Detail</h2>
<p>我是detail的元素,哈哈哈</p>
</div>
)
}
}
export default class App extends PureComponent {
render() {
return (
<div>
<Home/>
<Detail/>
</div>
)
}
}我们可以定义如下高阶组件:function logRenderTime(WrapperCpn) {
return class extends PureComponent {
UNSAFE_componentWillMount() {
this.begin = Date.now();
}
componentDidMount() {
this.end = Date.now();
const interval = this.end - this.begin;
console.log(`Home渲染使用时间:${interval}`)
}
render() {
return <WrapperCpn {...this.props}/>
}
}
}
const LogHome = logRenderTime(Home);
const LogDetail = logRenderTime(Detail);完整代码如下:import React, { PureComponent } from 'react';
function logRenderTime(WrapperCpn) {
return class extends PureComponent {
UNSAFE_componentWillMount() {
this.begin = Date.now();
}
componentDidMount() {
this.end = Date.now();
const interval = this.end - this.begin;
console.log(`${WrapperCpn.name}渲染使用时间:${interval}`)
}
render() {
return <WrapperCpn {...this.props}/>
}
}
}
class Home extends PureComponent {
render() {
return (
<div>
<h2>Home</h2>
<p>我是home的元素,哈哈哈</p>
</div>
)
}
}
class Detail extends PureComponent {
render() {
return (
<div>
<h2>Detail</h2>
<p>我是detail的元素,哈哈哈</p>
</div>
)
}
}
const LogHome = logRenderTime(Home);
const LogDetail = logRenderTime(Detail);
export default class App extends PureComponent {
render() {
return (
<div>
<LogHome />
<LogDetail />
</div>
)
}
}1.3. 高阶函数的意义我们会发现利用高阶组件可以针对某些React代码进行更加优雅的处理。其实早期的React有提供组件之间的一种复用方式是mixin,目前已经不再建议使用:Mixin 可能会相互依赖,相互耦合,不利于代码维护不同的Mixin中的方法可能会相互冲突Mixin非常多时,组件是可以感知到的,甚至还要为其做相关处理,这样会给代码造成滚雪球式的复杂性当然,HOC也有自己的一些缺陷:HOC需要在原组件上进行包裹或者嵌套,如果大量使用HOC,将会产生非常多的嵌套,这让调试变得非常困难;HOC可以劫持props,在不遵守约定的情况下也可能造成冲突;Hooks的出现,是开创性的,它解决了很多React之前的存在的问题,比如this指向问题、比如hoc的嵌套复杂度问题等等;后续我们还会专门来学习hooks相关的知识,敬请期待;二. 组件补充2.1. ref转发import React, { PureComponent, createRef } from 'react';
function Home(props) {
return (
<div>
<h2 ref={props.ref}>Home</h2>
<button>按钮</button>
</div>
)
}
export default class App extends PureComponent {
constructor(props) {
super(props);
this.homeTitleRef = createRef();
}
render() {
return (
<div>
<Home ref={this.homeTitleRef}/>
<button onClick={e => this.printInfo()}>打印ref</button>
</div>
)
}
printInfo() {
console.log(this.homeTitleRef);
}
}使用forwardRefimport React, { PureComponent, createRef, forwardRef } from 'react';
const Home = forwardRef(function(props, ref) {
return (
<div>
<h2 ref={ref}>Home</h2>
<button>按钮</button>
</div>
)
})
export default class App extends PureComponent {
constructor(props) {
super(props);
this.homeTitleRef = createRef();
}
render() {
return (
<div>
<Home ref={this.homeTitleRef}/>
<button onClick={e => this.printInfo()}>打印ref</button>
</div>
)
}
printInfo() {
console.log(this.homeTitleRef.current);
}
}2.2. Portals某些情况下,我们希望渲染的内容独立于父组件,甚至是独立于当前挂载到的DOM元素中(默认都是挂载到id为root的DOM元素上的)。Portal 提供了一种将子节点渲染到存在于父组件以外的 DOM 节点的优秀的方案:第一个参数(child)是任何可渲染的 React 子元素,例如一个元素,字符串或 fragment;第二个参数(container)是一个 DOM 元素;ReactDOM.createPortal(child, container)通常来讲,当你从组件的 render 方法返回一个元素时,该元素将被挂载到 DOM 节点中离其最近的父节点:render() {
// React 挂载了一个新的 div,并且把子元素渲染其中
return (
<div>
{this.props.children}
</div>
);
}然而,有时候将子元素插入到 DOM 节点中的不同位置也是有好处的:render() {
// React 并*没有*创建一个新的 div。它只是把子元素渲染到 `domNode` 中。
// `domNode` 是一个可以在任何位置的有效 DOM 节点。
return ReactDOM.createPortal(
this.props.children,
domNode
);
}比如说,我们准备开发一个Modal组件,它可以将它的子组件渲染到屏幕的中间位置:步骤一:修改index.html添加新的节点<div id="root"></div>
<!-- 新节点 -->
<div id="modal"></div>步骤二:编写这个节点的样式:#modal {
position: fixed;
left: 50%;
top: 50%;
transform: translate(-50%, -50%);
background-color: red;
}步骤三:编写组件代码import React, { PureComponent } from 'react';
import ReactDOM from 'react-dom';
class Modal extends PureComponent {
constructor(props) {
super(props);
}
render() {
return ReactDOM.createPortal(
this.props.children,
document.getElementById("modal")
)
}
}
export default class App extends PureComponent {
render() {
return (
<div>
<Modal>
<h2>我是标题</h2>
</Modal>
</div>
)
}
}2.3. Fragment在之前的开发中,我们总是在一个组件中返回内容时包裹一个div元素:export default class App extends PureComponent {
render() {
return (
<div>
<h2>当前计数: 0</h2>
<button>+1</button>
<button>-1</button>
</div>
)
}
}? 界面渲染我们会发现多了一个div元素:这个div元素对于某些场景是需要的(比如我们就希望放到一个div元素中,再针对性设置样式)某些场景下这个div是没有必要的,比如当前这里我可能希望所有的内容直接渲染到root中即可;我们可以删除这个div吗?? 删除div报错我们又希望可以不渲染这样一个div应该如何操作呢?使用FragmentFragment 允许你将子列表分组,而无需向 DOM 添加额外节点;? 使用Fragment效果React还提供了Fragment的段语法:它看起来像空标签 <> </>;export default class App extends PureComponent {
render() {
return (
<>
<h2>当前计数: 0</h2>
<button>+1</button>
<button>-1</button>
</>
)
}
}但是,如果我们需要在Fragment中添加key,那么就不能使用段语法:{
this.state.friends.map((item, index) => {
return (
<Fragment key={item.name}>
<div>{item.name}</div>
<div>{item.age}</div>
</Fragment>
)
})
}这里是不支持如下写法的:<key={item.name}>
<div>{item.name}</div>
<div>{item.age}</div>
</>2.4. StrictModeStrictMode 是一个用来突出显示应用程序中潜在问题的工具。与 Fragment 一样,StrictMode 不会渲染任何可见的 UI;它为其后代元素触发额外的检查和警告;严格模式检查仅在开发模式下运行;它们不会影响生产构建;可以为应用程序的任何部分启用严格模式:不会对 Header 和 Footer 组件运行严格模式检查;但是,ComponentOne 和 ComponentTwo 以及它们的所有后代元素都将进行检查;import React from 'react';
function ExampleApplication() {
return (
<div>
<Header />
<React.StrictMode>
<div>
<ComponentOne />
<ComponentTwo />
</div>
</React.StrictMode>
<Footer />
</div>
);
}但是检测,到底检测什么呢?1.识别不安全的生命周期:class Home extends PureComponent {
UNSAFE_componentWillMount() {
}
render() {
return <h2>Home</h2>
}
}? 警告信息2.使用过时的ref APIclass Home extends PureComponent {
UNSAFE_componentWillMount() {
}
render() {
return <h2 ref="home">Home</h2>
}
}? 警告信息3.使用废弃的findDOMNode方法在之前的React API中,可以通过findDOMNode来获取DOM,不过已经不推荐使用了,可以自行学习演练一下4.检查意外的副作用这个组件的constructor会被调用两次;这是严格模式下故意进行的操作,让你来查看在这里写的一些逻辑代码被调用多次时,是否会产生一些副作用;在生产环境中,是不会被调用两次的;class Home extends PureComponent {
constructor(props) {
super(props);
console.log("home constructor");
}
UNSAFE_componentWillMount() {
}
render() {
return <h2 ref="home">Home</h2>
}
}5.检测过时的context API早期的Context是通过static属性声明Context对象属性,通过getChildContext返回Context对象等方式来使用Context的;目前这种方式已经不推荐使用,大家可以自行学习了解一下它的用法;
JavaScript面向对象详解(一)
一. JavaScript的对象1.1. 传统对象 vs JavaScript对象传统的面向对象面向对象语言的一个标志就是类类是所有对象的统称, 是更高意义上的一种抽象. 对象是类的实例.通过类我们可以创建任意多个具体的对象.在学习C++/OC/Java/Python等编程语言的时候, 都可以按照这种方式去创建类和对象.JavaScript的面向对象JavaScript中没有类的概念(ES6之前),因此我们通常称为基于对象,而不是面向对象.虽然JavaScript中的基于对象也可以实现类似于类的封装、继承、甚至是多态。但是和传统意义的面向对象还是稍微有一些差异(后面我们会讲解它是如何实现的)ECMA中定义对象是这样: 无序属性的集合, 属性可以包含基本值, 对象或者函数.也就是对象是一组没有顺序的值组成的集合而已.对象的每个属性或者方法都有一个名字, 而名字对应一个值. 有没有觉得非常熟悉?没错, 其实就是我们经常看到和使用的映射(或者有些语言称为字典, 通常会使用哈希表来实现).1.2. 简单的方式创建对象创建自定义对象最简单的方式就是创建一个Object实例, 然后添加属性和方法// 1.创建person的对象
var person = new Object()
// 2.给person对象赋值了一些动态的属性和方法
person.name = "Coderwhy"
person.age = 18
person.height = 1.88
person.sayHello = function () {
alert("Hello, My name is " + this.name)
}
// 3.调用方法, 查看结果
person.sayHello()代码解析:步骤一: 创建一个名为person的对象.步骤二: 给对象动态的赋值了一些属性包括一个方法步骤三: 调用sayHello()方法, 主要看一下this.name会获取到谁呢? Coderwhy插播一个信息: 函数和方法的关系很多人在学习编程的时候, 会分不清楚什么是函数, 什么又是方法. 或者在什么情景下称为函数, 什么情景下称为方法.首先, 如果你看的是英文文档, 会有明显的区分: Function被称为函数, Method被称为方法.但是英文中, 为什么需要有这两个称呼呢?在早期的编程语言中, 只有函数(类似于数学中函数的称呼)后来有了面向对象语言, 面向对象语言中, 类中也可以定义函数. 但是人们为了区分在类中定义的函数, 通常称类中的函数为方法.还有一个非常重要的原因是, 通常方法中会携带一个调用者的当前对象(会将调用者作为参数一起传递进去), 也就是说this(有些语言中是self. 比如OC/Swift/Python等)当然, 你从这个角度来说, JavaScript中就没有函数了, 因为函数中都有this这样的参数. 但是通常来说, 我们还是会将封装到类中的函数称为方法, 而全局定义的函数称为函数.如果接触过Java的同学可能会知道Java中只有方法的程序, 没有函数的称呼. 学习过C语言的同学可能知道, C语言中只有函数的称呼, 没有方法的称呼.这就是因为Java中通常不定义全局函数, 但是在类中定义的. 而C语言不支持面向对象的编程.OK, 我们继续JavaScript面向对象之旅.前面创建对象的方式, 被早期的JavaScript程序员经常使用后来, 对象字面量称为创建这种对象的首选方式// 1.创建对象的字面量
var person = {
name: "Coderwhy",
age: 18,
height: 1.88,
sayHello: function () {
alert("My name is " + this.name)
}
}
// 2.调用对象的方法
person.sayHello()1.3. JavaScript中属性的特性JavaScript中关于属性有一个比较重要的概念: 属性描述符虽然我们开发中, 大多数情况不去可以的使用这些属性描述符但是某些情况下, 也确实会用到.建议大家先了解一下这些属性描述符, 以及它们的作用, 在以后用到时会非常有帮助.JavaScript中开始拥有了一种描述属性特征的特性(即属性描述符)。根据特性的不同,可以把属性分成两种类型:数据属性和访问器属性。常见的属性特性有哪些呢?[[Configurable]] // true or false表示能否通过delete删除属性从而重新定义属性,能否修改属性的特性,或者能否把属性修改为访问器属性。像前面例子中那样直接在对象上定义的属性,它们的这个特性默认值为true。[[Writable]] // true or false表示能否修改属性的值。像前面例子中那样直接在对象上定义的属性,它们的这个特性默认值为true。[[Enumerable]] // true or false表示能否通过for-in循环返回属性。像前面例子中那样直接在对象上定义的属性,它们的这个特性默认值为true。[[Value]] // everty thing包含这个属性的数据值。读取属性值的时候,从这个位置读;写入属性值的时候,把新值保存在这个位置。这个特性的默认值为undefined。[[set]] // function or undefined在写入属性时调用的函数。默认值为undefined。[[get]] // function or undefined在读取属性时调用的函数。默认值为undefined。这些属性特性是什么东西呢?从上面, 我们对这些特定的解释, 你会发现, 每个特定都会有自己特定的用途.比如Configurable当我们配置为false时, 就无法使用delete来删除该属性.设置属性特定obj: 将要被添加属性或修改属性的对象prop: 对象的属性descriptor: 对象属性的特性要想修改属性的特性,必须通过两个Object方法,即Object.defineProperty和Object.defineProperties正如其字面意思,这两个方法都是用来定义(修改)属性的,前者一次只能定义一个属性,后者则可以多个。defineProperty(obj, prop, descriptor)案例练习:var person = {}
Object.defineProperty(person, "birth", {
writable: false,
value: 2000
})
alert(person.birth) // 2000
person.birth = 1999
alert(person.birth) // 2000注意:在使用defineProperty方法定义新属性时(非修改旧属性),如果不指定,configurable, enumerable和writable特性的默认值都是false。也就是上面的代码等同于:var person = {};
Object.defineProperty(person, "birth", {
configurable: false,
enumerable: false,
writable: false,
value: 2000
});数据属性:数据属性包含一个数值的位置,在这个位置可以读取和写入值。数据属性拥有4个特性: [[Configurable]]/[[Enumerable]]/[[Writable]]/[[Value]]按照上面的方式, 我们定义的属性就是数据属性访问器属性:访问器属性不包含数据值,它们包含一对getter和setter函数。访问器属性不能直接定义,需要使用后面提到的Object.defineProperty函数定义。访问器属性也拥有4个特性: [[Configurable]]/[[Enumerable]]/[[Get]]/[[Set]]定义一个访问器属性:var person = {
birth: 2000,
age: 17
};
Object.defineProperty(person, 'year', {
get: function () {
return this.birth + this.age;
},
set: function (newValue) {
this.age = newValue - this.birth;
}
});
person.year = 2088
alert(person.age) // 88
person.age = 30
alert(person.year) // 2030注意: getter和setter都是可选的,在非严格模式下,只指定了getter却进行了写入操作,写入的值会被忽略; 只指定了setter却进行了读取操作,读取到的属性值为undefined。在严格模式下,则都会报错。二. JavaScript创建对象?虽然Object构造函数或对象字面量可以用来创建单个对象但是这些方式有个明显的缺点: 使用同一个接口创建很多对象, 会产生大量的重复代码.我们会有一些列的方式来解决这个问题, 最终得到我们最佳理想的方式来创建对象.2.1. 使用工厂模式工厂模式是一种非常常见的设计模式, 这种模式抽象了创建具体对象的过程.因为JavaScript中没法创建类, 开发人员就发明了一种函数, 用函数来封装以特定接口创建对象的细节.工厂模式创建对象:// 创建工厂函数
function createPerson(name, age, height) {
var o = new Object()
o.name = name
o.age = age
o.height = height
o.sayHello = function () {
alert("Hello, My name is " + this.name)
}
return o
}
// 创建两个对象
var person1 = createPerson("Coderwhy", 18, 1.88)
var person2 = createPerson("Kobe", 30, 1.98)
person1.sayHello() // Hello, My name is Coderwhy
person2.sayHello() // Hello, My name is Kobe代码解析:函数createPerson()能够根据接受的参数来构建一个包含所有必要信息的Person对象可以无数次地调用这个函数,而每次它都会返回一个包含三个属性一个方法的对象。工厂模式虽然解决了创建多个相似对象的问题,但却没有解决对象识别的问题(即怎样知道一个对象的类型)。随着JavaScript的发展,又一个新模式出现了。2.2. 构造函数模式JavaScript中的构造函数可用来创建特定类型的对象。像Object和Array这样的原生构造函数,在运行时会自动出现在执行环境中。此外,也可以创建自定义的构造函数,从而定义自定义对象类型的属性和方法。使用构造函数模式创建对象:// 构造函数
function Person(name, age, height) {
this.name = name
this.age = age
this.height = height
this.sayHello = function () {
alert(this.name)
}
}
// 使用构造函数创建对象
var person1 = new Person("Coderwhy", 18, 1.88)
var person2 = new Person("Kobe", 30, 1.98)
person1.sayHello() // Coderwhy
person2.sayHello() // Kobe代码解析:在这个例子中,Person()函数取代了createPerson()函数。我们会发现这个函数有一些不太一样的地方:没有显式地创建对象;(比如创建一个Object对象)直接将属性和方法赋给了this对象;没有return语句另外, 我们还注意到函数名Person使用的是大写字母P。按照惯例,构造函数始终都应该以一个大写字母开头,而非构造函数则应该以一个小写字母开头;这个做法借鉴自其他面向对象语言,主要是为了区别于ECMAScript中的其他函数;因为构造函数本身也是函数,只不过可以用来创建对象而已;还有, 我们在调用函数时, 不再只是简单的函数+(), 而是使用了new关键字这种方式调用构造函数实际上会经历以下4个步骤:创建一个新对象, 这个新的对象类型其实就是Person类型.将构造函数的作用域赋给新对象(因此this就指向了这个新对象,也就是this绑定);执行构造函数中的代码(为这个新对象添加属性和方法);返回新对象, 但是是默认返回的, 不需要使用return语句;在前面例子的最后,person1和person2分别保存着Person的一个不同的实例。这两个对象都有一个constructor(构造函数)属性,该属性指向Person.后面我们会详细说道constructor到底从何而来, 所以你需要特别知道一下这里有这个属性.// constructor属性
alert(person1.constructor === Person) // true
alert(person2.constructor === Person) // true我们也可以通过instanceof来查看它的类型注意: 我们会发现person1和person2既是Person类型, 也是Object类型.这是因为默认所有的对象都继承自Object.(关于继承, 后续详细讨论)// 使用instanceof查看是否是person或者Object类型
alert(person1 instanceof Object) // true
alert(person1 instanceof Person) // true
alert(person2 instanceof Object) // true
alert(person2 instanceof Person) // true2.3. 关于构造函数关于构造函数我们知道, 构造函数也是一个函数, 只是使用的方式和别的函数不太一样.(使用new)但是, 构造函数毕竟也是函数, 因此也可以像普通的函数一样去使用.而且, 其他任何的函数, 也可以通过new关键字来调用, 这个时候这个函数也可以被称为构造函数.把构造函数当做普通的函数去调用// 当做构造函数使用
var person = new Person("Coderwhy", 18, 1.88) // person对象
person.sayHello()
// 作为普通的函数调用
Person("Kobe", 30, 1.98) // window对象
window.sayHello()
// 在另外一个对象的作用域调用
var o = new Object()
Person.call(o, "Curry", 28, 1.93) // o对象
o.sayHello()构造函数来创建对象的缺陷:构造函数模式虽然好用,但也并非没有缺点。使用构造函数的主要问题,就是每个方法都要在每个实例上重新创建一遍。在前面的例子中,personl和person2都有一个名为sayName()的方法,但那两个方法不是同一个Function的实例。JavaScript中的函数也是对象,因此每定义一个函数,也就是实例化了一个对象构造函数的换一种形式:也就是上面的代码类似于下面的写法function Person(name, age, height) {
this.name = name
this.age = age
this.height = height
this.sayHello = new Function("alert(this.name)")
}有什么问题呢?从这个角度上来看构造函数,更容易明白每个Person实例都包含一个不同的Function实例.但是, 有必要创建多个Function实例吗? 它们执行的代码完全相同.你也许会考虑, 它们需要区分不同的对象, 不过, 在调用函数时, 我们传入的this就可以区分了. 没有必要创建出多个Function的实例.我们可以验证一下这是两个不同的函数:alert(person1.sayHello === person2.sayHello) // false有没有办法让它们是同一个函数呢? 使用全局函数即可// 定义全局和函数
function sayHello() {
alert(this.name)
}
// 构造函数
function Person(name, age, height) {
this.name = name
this.age = age
this.height = height
this.sayHello = sayHello
}
// 使用构造函数创建对象
var person1 = new Person("Coderwhy", 18, 1.88)
var person2 = new Person("Kobe", 30, 1.98)
alert(person1.sayHello === person2.sayHello) // true新的问题:这样做确实解决了两个函数做同一件事的问题,可是新问题又来了: 在全局作用域中定义的函数我们的目的却是只能被某个对象调用,这让全局作用域有点名不副实。而且我们进一步思考: 如果对象需要定义很多方法,那么就要定义很多个全局函数,于是我们这个自定义的引用类型就丝毫没有封装性可言了。有没有新的解决方案呢?使用原型模式.
组件构建原则(五):稳定抽象原则
背景介绍这是我的《架构整洁之道》系列的第十五篇,这篇文章的内容为稳定抽象原则。本篇文章高度依赖本系列的上一篇文章,文中的 SAP 也是上一篇文章的稳定依赖原则。《架构整洁之道》系列:寒草的《架构整洁之道》专栏稳定抽象原则一个组件的抽象化程度应该与其稳定性保持一致。高阶策略应该放在哪里在一个软件系统中,总有些部分是不应该经常发生变更的。这些部分通常用于表现该系统的高阶架构设计及一些策略相关的高阶决策。我们不想让这些业务决策和架构设计经常发生变更,因此这些代表了系统高阶策略的组件应该被放到稳定组 件(I=0)中,而不稳定的组件(I=1)中应该只包含那些我们想要快速和方便修改的部分。那么就有一个疑问:如果我们将高阶策略放入稳定组件中,那么用于描述那些策略的源代码就很难被修改了。这可能会导致整个系统的架构设计难于被修改。如何才能让一个无限稳定的组件(I=0)接受变更呢?这里就可以回顾一下之前的开闭原则:设计原则(三):OCP 开闭原则。那么有了这个原则为指导,创造一个足够灵活、能够被扩展,而且不需要修改的类是可以实现的。这时我们就可以用到抽象类。稳定抽象原则简介稳定抽象原则(SAP)为组件的稳定性与它的抽象化程度建立了一种关联。一方面,该原则要求稳定的组件同时应该是抽象的,这样它的稳定性就不会影响到扩展性。另一方面,该原则也要求一个不稳定的组件应该包含具体的实现代码,这样它的不稳定性就可以通过具体的代码被轻易修改。将 SAP 与 SDP 这两个原则结合起来,就等于组件层次上的 DIP。因为 SDP 要求的是让依赖关系指向更稳定的方向,而 SAP 则告诉我们稳定性本身就隐含了对抽象化的要求,即依赖关系应该指向更抽象的方向。DIP 毕竟是与类这个层次有关的原则,而一个类要么是抽象类,要么就不是。SDP 与 SAP 这对原则是应用在组件层面上的,我们要允许一个组件部分抽象,部分稳定。类只有两种可能,要么是抽象类,要么不是,而组件并不是“非黑即白”的,所以有了下面的衡量标准:衡量抽象化程度假设 A 指标是对组件抽象化程度的一个衡量,它的值是组件中抽象类与接口所占的比例。那么:Nc: 组件中类的数量。Na: 组件中抽象类和接口的数量。A: 抽象程度,A=Na÷NcA指标的取值范围是从[0, 1],值为0代表组件中没有任何抽象类,值为1就意味着组件中只有抽象类。主序列我们现在有了组件的稳定性 I与其抽象化程度 A ,就可以来定义他们两者之间的关系了。上图中最稳定的、包含了无限抽象类的组件应该位于左上角(0,1),最不稳定的、最具体的组件应该位于右下角(1,0)。我们不能强制要求所有的组件都处于(0,1)和(1,0)这两个位置上,那么就必须假设上图存在着一个合理组件的区间。而这个区间应该可以通过排除法推导出来,也就是说,我们可以先找出那些组件不应该处于的位置。下面我们来一起了解痛苦区和无用区~痛苦区假设某个组件处于(0,0)位置,那么它应该是一个非常稳定但也非常具体的组件。这样的组件在设计上是不佳的,因为它很难被修改,这意味着该组件不能被扩展。这样一来,因为这个组件不是抽象的,而且它又由于稳定性的原因变得特别难以被修改,我们并不希望一个设计良好的组件贴近这个区域,因此(0,0)周围的这个区域被我们称为痛苦区。不可变组件「比如工具型类库」落在(0,0)这一区域中是无害的,因为它们不太可能会发生变更。正因为如此,只有多变的软件组件落在痛苦区中才会造成麻烦。无用区我们来看看靠近(1,1)这一位置点的组件,这些组件通常是无限抽象的,但是没有被其他组件依赖,这样的组件往往 无法使用,因此我们将这个区域称为无用区。例如在系统的某个角落里某个没有人实现的抽象类,它们一直静静地躺在那里,没有人使用。落在无用区中的组件也一定会包含大量的无用代码,这并不是我们希望出现的情况。避开这两个区域主序列线:从(1,0)连接到(0,1)。坐落于主序列线上的组件不会为了追求稳定性而被设计得“太过抽象”,也不会为了避免抽象化而被设计得“太过不稳定”。这样的组件既不会特别难以被修改, 又可以实现足够的功能。对于这些组件来说,通常会有足够多的组件依赖于它们,这使得它们会具有一定程度的抽象,同时它们也依赖了足够多的其他组件,这又使得它一定会包含很多具体实现。在整条主序列线上,组件所能处于最优的位置是线的两端。然而,大型系统中的组件不可能做到完全抽象,也不可能做到完全稳定。所以我们只要追求让这些组件位于主序列线上,或者贴近这条线即可。离主序列线的距离D 指标:距离 D=|A+I-1|,该指标的取值范围是[O,1]。值为 0 意味着组件是直接位于主序列线上的,值为 1 则意味着组件在距离主序列最远的位置。通过计算每个组件的 D 指标,就可以量化一个系统设计与主序列的契合程度了。如下图:我们也可以跟踪一个组件随版本更迭 D 值的变化,假设 D=O.1 是组件的达标红线,R 组件的 2.1 版本的 D 值己经超出了红线范围,这就告诉我们现在值得花一些精力来找出这个组件偏离主序列线的原因了。结束语这样三条原则就结束了,他们的内涵主要为依赖关系管理的指标,可以被用来量化分析某个系统设计与“优秀”设计模式之间的契合度。???????????????少年向来不识天高地厚放眼处皆自负才高八斗虽是自命风流倒也坦诚无忧我爱这样的少年谦和而狂妄骄傲又坦然?????????????????
设计原则(六):DIP 依赖反转原则
背景介绍这是我的《架构整洁之道》系列的第十篇,这一篇我们将一起学习 DIP 依赖反转原则~高层策略性的代码不应该依赖实现底层细节的代码,恰恰相反,那些实现底层细节的代码应该依赖、高层策略性的代码。《架构整洁之道》系列:寒草的《架构整洁之道》专栏DIP 依赖反转原则依赖、反转原则(DIP)主要想告诉我们的是,如果想要设计一个灵活的系统,在源代码层次的依赖、关系中就应该多引用抽象类型,而非具体实现。显而易见,把这条设计原则当成金科玉律来加以严格执行是不现实的,因为软件系统在实际构造中不可避免地需要依赖到一些具体实现。我们主要应该关注的是软件系统内部那些会经常变动的具体实现模块,这些模块是不停开发的,也就会经常出现变更。稳定的抽象层我们每次修改抽象接口的时候,一定也会去修改对应的具体实现。但反过来,当我们修改具体实现时,却很少需要去修改相应的抽象接口。所以我们可以认为接口比实现更稳定。也就是说,如果想要在软件架构设计上边求稳定,就必须多使用稳定的抽象接口,少依赖多变的具体实现:应在代码中多使用抽象接口,尽量避免使用那些多变的具体实现类对此,我们通常会选择用抽象工厂(abstractfactory)这个设计模式不要在具体实现类上创建衍生类不要覆盖(override)包含具体实现的函数应避免在代码中写入与任何具体实现相关的名字,或者是其他容易变动的事物的名字工厂模式如果想要遵守上述编码守则,我们就必须要对那些易变对象的创建过程做一些特殊处理。在大部分面向对象编程语言中,人们都会选择用抽象工厂模式来解决这个源代码依赖的问题。如下图,Application 类是通过 Service 接口来使用 ConcreteImpl 类的。 然而,Application 类还是必须要构造 ConcreteImpl 类实例。于是,为了避免在源代码层次上引入对 ConcreteImpl 类具体实现的依赖,我们现在让 Application 类去调用 ServiceFactory 接口的 makeSvc 方法。这个方法就由 ServiceFactoryImpl 类来具体提供,它是 ServiceFactory 的一个衍生类。该方法的具体实现就是初始化一个 ConcreteImpl 类的实例,并且将其以 Service 类型返回。上面的例子中,具体实现组件的内部仅有一条依赖关系,这条关系其实是违反 DIP 的。这种情况很常见,我们在软件系统中并不可能完全消除违反 DIP 的情况。通常只需要把它们集中于少部分的具体实现组件中,将其与系统的其他部分隔离即可。绝大部分系统中都至少存在一个具体实现组件一一我们一般称之为 main 组件, 因为它们通常是 main 函数所在之处。上面的例子中,main 函数应该负责创建 ServiceFactoryimpl 实例,并将其赋值给类型为 ServiceFactory 的全局变量,以便让 Application 类通过这个全局变量来进行相关调用。// TODO:我没有完全理解上述例子,抽象工厂模式单独出一篇文章中间的那条曲线代表了软件架构中的抽象层与具体实现层的边界。在这里,所有跨越这条边界源代码级别的依赖关系都应该是单向的,即具体实现层依赖抽象层。上图中的这条曲线将整个系统划分为两部分组件:抽象接口与其具体实现。抽象接口组件:包含了应用的所有高阶业务规则具体实现组件:包括了所有这些业务规则所需要做的具体操作及其相关的细节信息DIP 被称为依赖反转原则的原因:控制流跨越架构边界的方向与源代码依赖关系跨越该边界的方向正好相反,源代码依赖方向永远是控制流方向的反转结束语在系统架构图中,DIP 通常是最显而易见的组织原则。我们在后续会把工厂模式章节图片中的那条曲线称为架构边界,而跨越边界的、朝向抽象层的单向依赖关系则会成为一个设计守则一一依赖守则。最后???????????????少年向来不识天高地厚放眼处皆自负才高八斗虽是自命风流倒也坦诚无忧我爱这样的少年谦和而狂妄骄傲又坦然?????????????????
UML建模与架构文档化
一、UML建模与架构文档化1、UML应用与未来从UML的早期版本开始, 便受到了计算机产业界的重视, OMG 的采纳和大公司的支持把 它推上了实际上的工业标准的地位, 使它拥有越来越多的用户。 它被广泛地用于应用领域和多 种类型的系统建模, ,如管理信息系统、 通信与控制系统、 嵌入式实时系统、分布式系统和系 统软件等。 近几年还被运用于软件再工程、 质量管理、 过程管理和配置管理等方面。 而且它 的应用不仅仅限于计算机软件, 还可用于非软件系统, 例如硬件设计、 业务处理流程、 企业 或事业单位的结构与行为建模。2、UML基础a.用例和用例图用例(usecase) 图内也翻译为用况、 用案等, 在 UML 中, 用例用一个椭圆表示,用例名往往用动 宾结构或主谓结构命名。b.交互图交互图 (interaction diagram) 是用来描述对象之间以及对象与参与者(actor) 之间的动态协作关系 以及协作过程中行为次序的图形文档。 它通常用来描述一个用例的行为,显示该用例中所涉及的对象和这 些对象之间的消息传递。交互图包括顺序图(sequence diagram) 和 协作图(collaboration diagram) 两种形式。 顺序图着重描述对象按照时间顺序的消息交换, 协作图着重描述系统成分如何协同工作。 顺 序图和协作图从不同的角度表达了系统中的交互和系统的行为, 它们之间可以相互转化。 一个用例需要多 个顺序图或协作图, 除非特别简单的用例。c.类图与对象图类是具有相似结构、 行为和关系的一组对象的抽象。 在定义类的时候, 类的命名应尽量用应用领域中的术语, 应明确、 无歧义, 以利于开发人员与用户之间的相互理 解和交流。 一般而言, 类的名字是合关系。 d.状态图和活动图3、基于UML 的软件开发过程UML是独立于软件开发过程的, 即 UML 能够在几乎任何一种软件开发过程中使用。迭代的渐进式软 件开发过程包含 4 个阶段, 即初启、细化、构建和部署。4、系统架构文档化软件架构用来处理软件高层次结构的设计和实施。 它以精心选择的形式将若干结构元素进行装配, 从 而满足系统主要功能和性能需求, 并满足其他非功能性需求, 如可靠性、 可伸缩性、 可移植性和可魚性。 Peny 和 Wolfe 使用一个精确的公式来表达, 该公式由 Boehm 做了进一步修改。 软件架构={元素, 形式, 关系/约束} 软件架构涉及到抽象、 分解和组合、 风格和美学。 我们用由多个视图或视角组成的模型来描述它。二、设计模式类之间的关系及原则一、类之间的关系(我拿Visio作图举例)1.继承关系是一个类(子接口,或子类)继承另-一个类(父接口,或父类)的功能,并可以增加它自己的新功能的能力。通过UML类图设计,继承用一条带空心三角箭头的实线表示,从子类指向父类,或者子接口指向父接口。2、实现关系实现指的是一个class类实现interface接口 (可以是多个)的功能,实现是类与接口之间最常见的关系。在C++中并没有接口的关键字,这种关系一般是通过声明纯虚函数来实现。通过UML类图设计,实现是用一条带空心三角箭头的虚线表示,从类指向实现的接口。3、依赖关系就是一个类A使用到另一个类B, 而这种使用关系是具有偶然性、临时性、非常弱的,但是B类的变化会影响到类A。在UML图类设计中,依赖关系用由类A指向类B的带前头虚线表示。4、关联关系体现的是两个类之间语义级别的一种强依赖关系,一般是长期性, 而且双方的关系是平等。关系可以是单向,双向的。使用UML类图设计,关联关系用由关联类A指向被关联类B的带箭头实线表示,在关联的两端可以标注双方的角色和多重性标记。上方可标注5、聚合关系是关联关系的一一种特例,它体现的是整体与部分的关系,即Has A关系。此时整体与部分之间是可分离的,它们可以具有各大自的生命周期,部分可以属于多个整体对象,也可以为多个整体对象共享。在UML类图设计中,聚合关系以空心菱形加实线箭头表示。6、组合关系也是关联关系的一种特例,它体现的是一种contains- -a的关系,这种关系比聚合关系更强,也成为强聚合。在UML.类图中,组合关系以实心萎形加实线箭头表示。二、设计模式的原则(简单列出)单一职責原则开放-封闭原则依赖倒转原则里氏代换原则接口隔离原则迪米特法则三、设计模式1.创建型模式类模式(工厂方法模式)对象模式(抽象工厂模式、建造者模式、原型模式、单例模式)2、结构型模式类模型(类,适配器模式)对象模式(适配器模式、桥接模式、组合模式,外观模式、装饰模式,享元模式、代理模式)3、行为型模式类模式(解释器模式、模板方法械)对象模式(命令模式、职责链模式、迭代器模式、状态模式、中介模式、观察者模式。策略模式、访问者模式、备忘录模式。例如:简单工厂模式主要用于创建对象,新添加类时,不会影响以前的系统代码。核心思想是用一具工厂来根据输入的条件产生不同的类,然后根据不同类的virtual函数得到不同的结果。优点:适用于不同情况创建不同的类时。缺点:客户端必须要知道基类和工厂类,耦合性井。需要根据不同需求创建不同的类,添加类的时候需要爱护工厂类。小学生的四则运算法则为例,对应UML.类如下:下面简单做一个UML模型和C++类实例来实现一下代码实现:头文件:// 设计操作基类
template <typename T>
class COperator
{
public:
virtual T getResult() = 0;
virtual void setArgs(T lpa, T rpa);
protected:
T lah, rah;
};
template <typename T>
void COperator<T>::setArgs(T lpa,T rpa)
{
lah = lpa;
rah = rpa;
}
// 加法模板类
template<typename T>
class CAddOperator :public COperator<T>
{
public:
virtual T getResult()
{
return COperator<T>::lah + COperator<T>::rah;
}
};
// 减法模板类
template<typename T>
class CSubOperator :public COperator<T>
{
public:
virtual T getResult()
{
return COperator<T>::lah - COperator<T>::rah;
}
};
// 乘法模板类
template<typename T>
class CMulOperator :public COperator<T>
{
public:
virtual T getResult()
{
return COperator<T>::lah * COperator<T>::rah;
}
};
// 除法模板类
template<typename T>
class CDivOperator :public COperator<T>
{
public:
virtual T getResult()
{
if (0 == COperator<T>::rah)
{
std::cout << "除数不能为0" << std::endl;
return 0;
}
return COperator<T>::lah / COperator<T>::rah;
}
};
// 工厂类
template<typename T>
class CCalculatorFactory
{
public:
static COperator<T>*createOjbect(char c);
};
template<typename T>
COperator<T> *CCalculatorFactory<T>::createOjbect(char c)
{
COperator<T> *oper;
switch (c)
{
case '+':
oper = new CAddOperator<T>();
break;
case '-':
oper = new CSubOperator<T>();
break;
case '*':
oper = new CAddOperator<T>();
break;
case '/':
oper = new CAddOperator<T>();
break;
default:
oper = new CAddOperator<T>();
break;
}
return oper;
}
源文件:#include <iostream>
#include "SFMHeader.h"
using namespace std;
int main()
{
// 创建对象
COperator<double> *p = CCalculatorFactory<double>::createOjbect('+');
p->setArgs(23884.89989, 4324.234234);
cout <<"两数相加结果为:"<<p->getResult() << endl;
delete p;
return 0;
}
MVVM模式
MVVM模式 什么是MVVM模式? MVVM(Model-View-ViewModel)是一种软件架构设计模式,由微软 WPF(用于替代 WinForm,以 前就是用这个技术开发桌面应用程序的)和 Silverlight(类似于 Java Applet,简单点说就是在浏览器上 运行的 WPF) 的架构师 Ken Cooper 和 Ted Peters 开发,是一种简化用户界面的事件驱动编程方式。 由 John Gossman(同样也是 WPF 和 Silverlight 的架构师)于 2005 年在他的博客上发表。 MVVM 源自于经典的 MVC(Model-View-Controller)模式。MVVM 的核心是 ViewModel 层,负责转 换 Model 中的数据对象来让数据变得更容易管理和使用,其作用如下: 该层向上与视图层进行双向数据绑定 向下与 Model 层通过接口请求进行数据交互MVVM 已经相当成熟了,当下流行的 MVVM 框架有 Vue.js , AngularJS 等。 为什么要使用 MVVM MVVM 模式和 MVC 模式一样,主要目的是分离视图(View)和模型(Model),有几大好处:低耦合: 视图(View)可以独立于 Model 变化和修改,一个 ViewModel 可以绑定到不同的 View 上,当 View 变化的时候 Model 可以不变,当 Model 变化的时候 View 也可以不变。 可复用: 你可以把一些视图逻辑放在一个 ViewModel 里面,让很多 View 重用这段视图逻辑。独立开发: 开发人员可以专注于业务逻辑和数据的开发(ViewModel),设计人员可以专注于页 面设计。 可测试: 界面素来是比较难于测试的,而现在测试可以针对 ViewModel 来写。 MVVM 的组成部分Runtime Compiler及时运行及时编译View: View 是视图层,也就是用户界面。前端主要由 HTML 和 CSS 来构建,为了更方便地展现 ViewModel 或者 Model层的数据,已经产生了各种各样的前后端模板语言,比如 FreeMarker、 Thymeleaf 等等,各大 MVVM 框架如 Vue.js,AngularJS,EJS 等也都有自己用来构建用户界面的内置 模板语言。 Model: Model 是指数据模型,泛指后端进行的各种业务逻辑处理和数据操控,主要围绕数据库系统展开。这里 的难点主要在于需要和前端约定统一的 接口规则 ViewModel: ViewModel 是由前端开发人员组织生成和维护的视图数据层。在这一层,前端开发者对从后端获取的 Model 数据进行转换处理,做二次封装,以生成符合 View 层使用预期的视图数据模型需要注意的是 ViewModel 所封装出来的数据模型包括视图的状态和行为两部分,而 Model 层的数据模 型是只包含状态的。比如页面的这一块展示什么,那一块展示什么这些都属于视图状态(展示) 页面加载进来时发生什么,点击这一块发生什么,这一块滚动时发生什么这些都属于视图行为(交 互) 视图状态和行为都封装在了 ViewModel 里。这样的封装使得 ViewModel 可以完整地去描述 View 层。 由于实现了双向绑定,ViewModel 的内容会实时展现在 View 层,这是激动人心的,因为前端开发者再 也不必低效又麻烦地通过操纵 DOM 去更新视图。 MVVM 框架已经把最脏最累的一块做好了,我们开发者只需要处理和维护 ViewModel,更新数据视图就 会自动得到相应更新,真正实现 事件驱动编程 。 View 层展现的不是 Model 层的数据,而是 ViewModel 的数据,由 ViewModel 负责与 Model 层交互,这就完全解耦了 View 层和 Model 层,这个解耦是至关重要的,它是前后端分离方 案实施的重要一环。