Skip to content

Commit 90e56cf

Browse files
committedOct 9, 2019
no message
1 parent 5fb6788 commit 90e56cf

10 files changed

+646
-0
lines changed
 

‎结构型模式-mvc模式.py

+67
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,67 @@
1+
# -*- coding: utf-8 -*-
2+
# @Author : ydf
3+
# @Time : 2019/10/8 0008 21:23
4+
"""
5+
MVC 模式代表 Model-View-Controller(模型-视图-控制器) 模式。这种模式用于应用程序的分层开发。
6+
7+
Model(模型) - 模型代表一个存取数据的对象或 JAVA POJO。它也可以带有逻辑,在数据变化时更新控制器。
8+
View(视图) - 视图代表模型包含的数据的可视化。
9+
Controller(控制器) - 控制器作用于模型和视图上。它控制数据流向模型对象,并在数据变化时更新视图。它使视图与模型分离开。
10+
"""
11+
from monkey_print2 import print
12+
13+
quotes = ('A man is not complete until he is married. Then he is finished.',
14+
'As I said before, I never repeat myself.',
15+
'Behind a successful man is an exhausted woman.',
16+
'Black holes really suck...', 'Facts are stubborn things.')
17+
18+
19+
class QuoteModel:
20+
21+
def get_quote(self, n):
22+
try:
23+
value = quotes[n]
24+
except IndexError as err:
25+
value = 'Not found!'
26+
return value
27+
28+
29+
class QuoteTerminalView:
30+
31+
def show(self, quote):
32+
print('And the quote is: "{}"'.format(quote))
33+
34+
def error(self, msg):
35+
print('Error: {}'.format(msg))
36+
37+
def select_quote(self):
38+
return input('Which quote number would you like to see?')
39+
40+
41+
class QuoteTerminalController:
42+
43+
def __init__(self):
44+
self.model = QuoteModel()
45+
self.view = QuoteTerminalView()
46+
47+
def run(self):
48+
valid_input = False
49+
while not valid_input:
50+
n = self.view.select_quote()
51+
try:
52+
n = int(n)
53+
except ValueError as err:
54+
self.view.error("Incorrect index '{}'".format(n))
55+
else:
56+
valid_input = True
57+
quote = self.model.get_quote(n)
58+
self.view.show(quote)
59+
60+
61+
def main():
62+
controller = QuoteTerminalController()
63+
while True:
64+
controller.run()
65+
66+
if __name__ == '__main__':
67+
main()

‎结构型模式-代理模式.py

+66
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,66 @@
1+
# -*- coding: utf-8 -*-
2+
# @Author : ydf
3+
# @Time : 2019/10/8 0008 22:40
4+
5+
"""
6+
在某些应用中,我们想要在访问某个对象之前执行一个或多个重要的操作,例如,访问敏感
7+
信息——在允许用户访问敏感信息之前,我们希望确保用户具备足够的权限。操作系统中也存在
8+
类似的情况,用户必须具有管理员权限才能在系统中安装新程序。
9+
上面提到的重要操作不一定与安全问题相关。延迟初始化
10+
是另一个案例:我们想要把一个计算成本较高的对象的创建过程延迟到用户首次真正使用它时
11+
才进行。
12+
13+
14+
15+
在代理模式(Proxy Pattern)中,一个类代表另一个类的功能。这种类型的设计模式属于结构型模式。
16+
17+
在代理模式中,我们创建具有现有对象的对象,以便向外界提供功能接口。
18+
"""
19+
20+
21+
class SensitiveInfo:
22+
23+
def __init__(self):
24+
self.users = ['nick', 'tom', 'ben', 'mike']
25+
26+
def read(self):
27+
print('There are {} users: {}'.format(len(self.users), ' '.join(self.users)))
28+
29+
def add(self, user):
30+
self.users.append(user)
31+
print('Added user {}'.format(user))
32+
33+
34+
class Info:
35+
36+
'''SensitiveInfo的保护代理'''
37+
38+
def __init__(self):
39+
self.protected = SensitiveInfo()
40+
self.secret = '0xdeadbeef'
41+
42+
def read(self):
43+
self.protected.read()
44+
45+
def add(self, user):
46+
sec = input('what is the secret? ')
47+
self.protected.add(user) if sec == self.secret else print("That's wrong!")
48+
49+
50+
def main():
51+
info = Info()
52+
while True:
53+
print('1. read list |==| 2. add user |==| 3. quit')
54+
key = input('choose option: ')
55+
if key == '1':
56+
info.read()
57+
elif key == '2':
58+
name = input('choose username: ')
59+
info.add(name)
60+
elif key == '3':
61+
exit()
62+
else:
63+
print('unknown option: {}'.format(key))
64+
65+
if __name__ == '__main__':
66+
main()

‎行为型模式-发布订阅模式.py

+3
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
# -*- coding: utf-8 -*-
2+
# @Author : ydf
3+
# @Time : 2019/10/9 0009 10:39

‎行为型模式-命令模式.py

+90
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,90 @@
1+
# -*- coding: utf-8 -*-
2+
# @Author : ydf
3+
# @Time : 2019/10/9 0009 9:29
4+
"""
5+
命令模式(Command Pattern)是一种数据驱动的设计模式,它属于行为型模式。请求以命令的形式包裹在对象中,并传给调用对象。调用对象寻找可以处理该命令的合适的对象,并把该命令传给相应的对象,该对象执行命令。
6+
7+
8+
现在多数应用都有撤销操作。虽然难以想象,但在很多年里,任何软件中确实都不存在撤销
9+
操作。撤销操作是在1974年引入的(请参考网页[t.cn/Rqr3N22]),但Fortran和Lisp分别早在1957
10+
年和1958年就已创建了撤销操作(请参考网页[t.cn/Rqr3067]),这两门语言仍在被人广泛使用。
11+
在那些年里,我真心不想使用应用软件。犯了一个错误,用户也没什么便捷方式能修正它。
12+
"""
13+
import os
14+
15+
verbose = True
16+
17+
18+
class RenameFile:
19+
20+
def __init__(self, path_src, path_dest):
21+
self.src, self.dest = path_src, path_dest
22+
23+
def execute(self):
24+
if verbose:
25+
print("[renaming '{}' to '{}']".format(self.src, self.dest))
26+
os.rename(self.src, self.dest)
27+
28+
def undo(self):
29+
if verbose:
30+
print("[renaming '{}' back to '{}']".format(self.dest, self.src))
31+
os.rename(self.dest, self.src)
32+
33+
34+
class CreateFile:
35+
36+
def __init__(self, path, txt='hello world\n'):
37+
self.path, self.txt = path, txt
38+
39+
def execute(self):
40+
if verbose:
41+
print("[creating file '{}']".format(self.path))
42+
with open(self.path, mode='w', encoding='utf-8') as out_file:
43+
out_file.write(self.txt)
44+
45+
def undo(self):
46+
delete_file(self.path)
47+
48+
49+
class ReadFile:
50+
51+
def __init__(self, path):
52+
self.path = path
53+
54+
def execute(self):
55+
if verbose:
56+
print("[reading file '{}']".format(self.path))
57+
with open(self.path, mode='r', encoding='utf-8') as in_file:
58+
print(in_file.read(), end='')
59+
60+
61+
def delete_file(path):
62+
if verbose:
63+
print("deleting file '{}'".format(path))
64+
os.remove(path)
65+
66+
67+
def main():
68+
orig_name, new_name = 'file1', 'file2'
69+
70+
commands = []
71+
for cmd in CreateFile(orig_name), ReadFile(orig_name), RenameFile(orig_name, new_name):
72+
commands.append(cmd)
73+
74+
[c.execute() for c in commands]
75+
76+
answer = input('reverse the executed commands? [y/n] ')
77+
78+
if answer not in 'yY':
79+
print("the result is {}".format(new_name))
80+
exit()
81+
82+
for c in reversed(commands):
83+
try:
84+
c.undo()
85+
except AttributeError as e:
86+
pass
87+
88+
89+
if __name__ == '__main__':
90+
main()

‎行为型模式-模板模式.py

+75
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,75 @@
1+
# -*- coding: utf-8 -*-
2+
# @Author : ydf
3+
# @Time : 2019/10/9 0009 10:28
4+
"""重要程度 ☆☆☆☆☆
5+
非常简单自然的一种设计模式,说白了就是继承。策略模式说罢了就是组合。
6+
配合oop 4步转化公式,所向披靡,适用范围广。
7+
8+
1.场景:
9+
10+
                        1.1 当多个算法或类实现类似或相同逻辑的时候。
11+
12+
                        1.2 在子类中实现算法有助于减少重复代码的时候。
13+
14+
                        1.3 可以让子类利用覆盖实现行为来定义多个算法的时候。
15+
16+
2.目的:
17+
18+
                        2.1 使用基本操作定义算法的框架。
19+
20+
                        2.2 重新定义子类的某些操作,而无需修改算法的结构。
21+
22+
                        2.3 实现代码重用并避免重复工作
23+
24+
                        2.4 利用通用接口或实现
25+
26+
"""
27+
28+
from abc import ABCMeta, abstractmethod
29+
from monkey_print2 import print
30+
31+
32+
# 抽象方法 AbstractClass
33+
class AbstractClass(metaclass=ABCMeta):
34+
def __init__(self):
35+
pass
36+
37+
@abstractmethod
38+
def operation1(self):
39+
pass
40+
41+
@abstractmethod
42+
def operation2(self):
43+
pass
44+
45+
def operation3(self):
46+
print('操作3')
47+
48+
# 模板方法 tmplate_method()
49+
def template_method(self):
50+
print("Defining the Algorithm.Operation1 follows Operation2")
51+
self.operation2()
52+
self.operation1()
53+
self.operation3()
54+
55+
56+
# 具体类 ConcreteClass
57+
class ConcreteClass(AbstractClass):
58+
def operation1(self):
59+
print("My Concrete Operation1")
60+
61+
def operation2(self):
62+
print("Operation 2 remains same")
63+
64+
65+
if __name__ == '__main__':
66+
concreate = ConcreteClass()
67+
concreate.template_method()
68+
69+
"""
70+
"D:/coding2/python36patterns/行为型模式-模板模式.py:47" 10:35:10 Defining the Algorithm.Operation1 follows Operation2
71+
"D:/coding2/python36patterns/行为型模式-模板模式.py:59" 10:35:10 Operation 2 remains same
72+
"D:/coding2/python36patterns/行为型模式-模板模式.py:56" 10:35:10 My Concrete Operation1
73+
"D:/coding2/python36patterns/行为型模式-模板模式.py:43" 10:35:10 操作3
74+
75+
"""

‎行为型模式-状态模式.py

+82
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,82 @@
1+
# -*- coding: utf-8 -*-
2+
# @Author : ydf
3+
# @Time : 2019/10/9 0009 10:08
4+
"""
5+
在状态模式(State Pattern)中,类的行为是基于它的状态改变的。这种类型的设计模式属于行为型模式。
6+
7+
在状态模式中,我们创建表示各种状态的对象和一个行为随着状态对象改变而改变的 context 对象。
8+
"""
9+
10+
11+
class State(object):
12+
"""Base state. This is to share functionality"""
13+
14+
def scan(self):
15+
"""Scan the dial to the next station"""
16+
self.pos += 1
17+
if self.pos == len(self.stations):
18+
self.pos = 0
19+
print("Scanning... Station is", self.stations[self.pos], self.name)
20+
21+
22+
class AmState(State):
23+
def __init__(self, radio):
24+
self.radio = radio
25+
self.stations = ["1250", "1380", "1510"]
26+
self.pos = 0
27+
self.name = "AM"
28+
29+
def toggle_amfm(self):
30+
print("Switching to FM")
31+
self.radio.state = self.radio.fmstate
32+
33+
34+
class FmState(State):
35+
def __init__(self, radio):
36+
self.radio = radio
37+
self.stations = ["81.3", "89.1", "103.9"]
38+
self.pos = 0
39+
self.name = "FM"
40+
41+
def toggle_amfm(self):
42+
print("Switching to AM")
43+
self.radio.state = self.radio.amstate
44+
45+
46+
class Radio(object):
47+
"""A radio. It has a scan button, and an AM/FM toggle switch."""
48+
49+
def __init__(self):
50+
"""We have an AM state and an FM state"""
51+
self.amstate = AmState(self)
52+
self.fmstate = FmState(self)
53+
self.state = self.amstate
54+
55+
def toggle_amfm(self):
56+
self.state.toggle_amfm()
57+
58+
def scan(self):
59+
self.state.scan()
60+
61+
62+
# Test our radio out
63+
if __name__ == '__main__':
64+
radio = Radio()
65+
actions = [radio.scan] * 2 + [radio.toggle_amfm] + [radio.scan] * 2
66+
actions = actions * 2
67+
68+
for action in actions:
69+
action()
70+
71+
"""
72+
Scanning... Station is 1380 AM
73+
Scanning... Station is 1510 AM
74+
Switching to FM
75+
Scanning... Station is 89.1 FM
76+
Scanning... Station is 103.9 FM
77+
Scanning... Station is 81.3 FM
78+
Scanning... Station is 89.1 FM
79+
Switching to AM
80+
Scanning... Station is 1250 AM
81+
Scanning... Station is 1380 AM
82+
"""

‎行为型模式-策略模式.py

+50
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,50 @@
1+
# -*- coding: utf-8 -*-
2+
# @Author : ydf
3+
# @Time : 2019/10/9 0009 10:12
4+
"""重要程度 ☆☆☆☆
5+
6+
策略模式(Strategy pattern)鼓励使用多种算法来解决一个问题,其杀手级特性是能够在运
7+
行时透明地切换算法(客户端代码对变化无感知)。因此,如果你有两种算法,并且知道其中一
8+
种对少量输入效果更好,另一种对大量输入效果更好,则可以使用策略模式在运行时基于输入数
9+
据决定使用哪种算法
10+
11+
以下演示策略类。在python中函数也是一等公民。简单情况下的策略模式将函数本身作为另一个函数/方法的入参即可。
12+
13+
"""
14+
15+
from monkey_print2 import print
16+
17+
# 策略模式
18+
class Strategy():
19+
def process(self):
20+
pass
21+
22+
23+
class FaultStrategy(Strategy):
24+
def process(self):
25+
print("fault")
26+
27+
28+
class NormalStrategy(Strategy):
29+
def process(self):
30+
print("normal")
31+
32+
33+
class Park():
34+
def __init__(self, strategy):
35+
self.__strategy = strategy
36+
37+
def geoProcess(self):
38+
self.__strategy.process()
39+
40+
41+
if __name__ == '__main__':
42+
p = Park(NormalStrategy())
43+
p.geoProcess()
44+
p = Park(FaultStrategy())
45+
p.geoProcess()
46+
47+
"""
48+
"D:/coding2/python36patterns/行为型模式-策略模式.py:30" 10:23:25 normal
49+
"D:/coding2/python36patterns/行为型模式-策略模式.py:25" 10:23:25 fault
50+
"""
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,75 @@
1+
# -*- coding: utf-8 -*-
2+
# @Author : ydf
3+
# @Time : 2019/10/9 0009 9:39
4+
5+
"""重要程度 ☆☆☆
6+
当对象间存在一对多关系时,则使用观察者模式(Observer Pattern)。比如,当一个对象被修改时,则会自动通知它的依赖对象。观察者模式属于行为型模式。
7+
有时,我们希望在一个对象的状态改变时更新另外一组对象。
8+
9+
说这么多抽象的概念,说点具体的就是以日志为例,有人把print当做日志用,里面的区别相当大,日志不仅可以有streamhandler和filehandler,还有mailhandler httphandler 等几亿种自定义handler。logger debug时候自动触发各种handler的emit方法。
10+
用不会日志不理解日志,对logger addHandler各种handler懵逼不知道在干嘛,最主要是不懂观察者模式造成的。
11+
希望举得例子是接近真实有意义,下面简单使用观察者模式重新模拟实现一个伪日志包。
12+
"""
13+
import abc
14+
from monkey_print2 import print
15+
16+
17+
class AbstractHandler(metaclass=abc.ABCMeta):
18+
@abc.abstractmethod
19+
def emit(self, record):
20+
pass
21+
22+
23+
class Logger:
24+
def __init__(self, logger_name):
25+
self.name = logger_name
26+
self.handlers = []
27+
28+
def add_handler(self, handler):
29+
self.handlers.append(handler)
30+
31+
def log(self, record: str):
32+
for hr in self.handlers:
33+
hr.emit(f'{self.name} -- {record}')
34+
35+
36+
class StreamHandler(AbstractHandler):
37+
def emit(self, record):
38+
print(f' {record} 控制台打印')
39+
40+
41+
class FileHandler(AbstractHandler):
42+
def emit(self, record):
43+
print(f' {record} 文件写入') # 只是为了演示写入文件,用print模拟的伪实现。希望要搞清楚这里的目的不是print。
44+
45+
46+
class MailHandler(AbstractHandler):
47+
def emit(self, record):
48+
print(f' {record} 发邮件给某人') # 只是为了演示发邮件,用print模拟的伪实现。希望要搞清楚这里的目的不是print。
49+
50+
51+
class DingdingHandler(AbstractHandler):
52+
def emit(self, record):
53+
print(f' {record} 钉钉机器人将这句话发给群里') # 只是为了演示发钉钉机器人消息,用print模拟的伪实现。希望要搞清楚这里的目的不是print。
54+
55+
56+
if __name__ == '__main__':
57+
logger1 = Logger('a')
58+
logger1.add_handler(StreamHandler())
59+
logger1.add_handler(FileHandler())
60+
logger1.add_handler(MailHandler())
61+
logger1.log('啦啦啦啦啦啦')
62+
63+
logger2 = Logger('b')
64+
logger2.add_handler(StreamHandler())
65+
logger2.add_handler(DingdingHandler())
66+
logger2.log('哈哈哈哈哈哈')
67+
68+
"""
69+
可以看到日志非常灵活,可以按需选择几个handler,例如日志a会 控制台打印版 写入文件 发邮件,日志b控制台打印 发钉钉消息。
70+
"D:/coding2/python36patterns/行为型模式-观察者模式.py:36" 10:00:19 a -- 啦啦啦啦啦啦 控制台打印
71+
"D:/coding2/python36patterns/行为型模式-观察者模式.py:40" 10:00:19 a -- 啦啦啦啦啦啦 文件写入
72+
"D:/coding2/python36patterns/行为型模式-观察者模式.py:45" 10:00:19 a -- 啦啦啦啦啦啦 发邮件给某人
73+
"D:/coding2/python36patterns/行为型模式-观察者模式.py:36" 10:00:19 b -- 哈哈哈哈哈哈 控制台打印
74+
"D:/coding2/python36patterns/行为型模式-观察者模式.py:49" 10:00:19 b -- 哈哈哈哈哈哈 钉钉机器人将这句话发给群里
75+
"""

‎行为型模式-解释器模式.py

+65
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,65 @@
1+
# -*- coding: utf-8 -*-
2+
# @Author : ydf
3+
# @Time : 2019/10/9 0009 9:35
4+
"""
5+
重要程度 ☆☆
6+
7+
解释器模式(Interpreter Pattern)提供了评估语言的语法或表达式的方式,它属于行为型模式。这种模式实现了一个表达式接口,该接口解释一个特定的上下文。这种模式被用在 SQL 解析、符号处理引擎等。
8+
9+
对每个应用来说,至少有以下两种不同的用户分类。
10+
 基本用户:这类用户只希望能够凭直觉使用应用。他们不喜欢花太多时间配置或学习应
11+
用的内部。对他们来说,基本的用法就足够了。
12+
 高级用户:这些用户,实际上通常是少数,不介意花费额外的时间学习如何使用应用的
13+
高级特性。如果知道学会之后能得到以下好处,他们甚至会去学习一种配置(或脚本)
14+
语言。
15+
 能够更好地控制一个应用
16+
 以更好的方式表达想法
17+
 提高生产力
18+
解释器(Interpreter)模式仅能引起应用的高级用户的兴趣。这是因为解释器模式背后的主
19+
要思想是让非初级用户和领域专家使用一门简单的语言来表达想法。然而,什么是一种简单的语
20+
言?对于我们的需求来说,一种简单的语言就是没编程语言那么复杂的语言
21+
"""
22+
from monkey_print2 import print
23+
24+
25+
class PlayContext():
26+
play_text = None
27+
28+
29+
class Expression():
30+
def interpret(self, context):
31+
if len(context.play_text) == 0:
32+
return
33+
else:
34+
play_segs = context.play_text.split(" ")
35+
for play_seg in play_segs:
36+
pos = 0
37+
for ele in play_seg:
38+
if ele.isalpha():
39+
pos += 1
40+
continue
41+
break
42+
play_chord = play_seg[0:pos]
43+
play_value = play_seg[pos:]
44+
self.execute(play_chord, play_value)
45+
46+
def execute(self, play_key, play_value):
47+
pass
48+
49+
50+
class NormGuitar(Expression):
51+
def execute(self, key, value):
52+
print("Normal Guitar Playing--Chord:%s Play Tune:%s" % (key, value))
53+
54+
55+
if __name__ == "__main__":
56+
context = PlayContext()
57+
context.play_text = "C53231323 Em43231323 F43231323 G63231323"
58+
guitar = NormGuitar()
59+
guitar.interpret(context)
60+
"""
61+
"D:/coding2/python36patterns/行为型模式-解释器模式.py:29" 09:37:28 Normal Guitar Playing--Chord:C Play Tune:53231323
62+
"D:/coding2/python36patterns/行为型模式-解释器模式.py:29" 09:37:28 Normal Guitar Playing--Chord:Em Play Tune:43231323
63+
"D:/coding2/python36patterns/行为型模式-解释器模式.py:29" 09:37:28 Normal Guitar Playing--Chord:F Play Tune:43231323
64+
"D:/coding2/python36patterns/行为型模式-解释器模式.py:29" 09:37:28 Normal Guitar Playing--Chord:G Play Tune:63231323
65+
"""

‎行为型模式-责任链模式.py

+73
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,73 @@
1+
# -*- coding: utf-8 -*-
2+
# @Author : ydf
3+
# @Time : 2019/10/9 0009 9:24
4+
5+
""" 重要程度 ☆☆
6+
顾名思义,责任链模式(Chain of Responsibility Pattern)为请求创建了一个接收者对象的链。这种模式给予请求的类型,对请求的发送者和接收者进行解耦。这种类型的设计模式属于行为型模式。
7+
8+
在这种模式中,通常每个接收者都包含对另一个接收者的引用。如果一个对象不能处理该请求,那么它会把相同的请求传给下一个接收者,依此类推。
9+
10+
11+
"""
12+
class Event:
13+
14+
def __init__(self, name):
15+
self.name = name
16+
17+
def __str__(self):
18+
return self.name
19+
20+
21+
class Widget:
22+
23+
def __init__(self, parent=None):
24+
self.parent = parent
25+
26+
def handle(self, event):
27+
handler = 'handle_{}'.format(event)
28+
if hasattr(self, handler):
29+
method = getattr(self, handler)
30+
method(event)
31+
elif self.parent:
32+
self.parent.handle(event)
33+
elif hasattr(self, 'handle_default'):
34+
self.handle_default(event)
35+
36+
37+
class MainWindow(Widget):
38+
39+
def handle_close(self, event):
40+
print('MainWindow: {}'.format(event))
41+
42+
def handle_default(self, event):
43+
print('MainWindow Default: {}'.format(event))
44+
45+
46+
class SendDialog(Widget):
47+
48+
def handle_paint(self, event):
49+
print('SendDialog: {}'.format(event))
50+
51+
52+
class MsgText(Widget):
53+
54+
def handle_down(self, event):
55+
print('MsgText: {}'.format(event))
56+
57+
58+
def main():
59+
mw = MainWindow()
60+
sd = SendDialog(mw)
61+
msg = MsgText(sd)
62+
63+
for e in ('down', 'paint', 'unhandled', 'close'):
64+
evt = Event(e)
65+
print('\nSending event -{}- to MainWindow'.format(evt))
66+
mw.handle(evt)
67+
print('Sending event -{}- to SendDialog'.format(evt))
68+
sd.handle(evt)
69+
print('Sending event -{}- to MsgText'.format(evt))
70+
msg.handle(evt)
71+
72+
if __name__ == '__main__':
73+
main()

0 commit comments

Comments
 (0)
Please sign in to comment.