balcony是什么意思?全文共9402字,预计学习时长19分钟
图片来源:https://unsplash.com/@marcelheil
Python是数据科学中一种十分常用的编程语言。对一些人来讲,它的语言灵活、可读性强,对另一些人来讲,它简单易上手,对大多数人来讲,是由于它的多面性。
我们将Python称为多面语言,因为它允许使用四种不同的编码规范进行编码:功能性、命令性、面向对象和面向过程。这些编码风格被正式称为编程范例(
https://en.wikipedia.org/wiki/Programming_paradigm),代表了一种根据语言特性对语言进行分类的方法。
本文将对面向对象编程( OOP )(
https://en.wikipedia.org/wiki/Object-oriented_programming)进行研究。
什么是面向对象编程( OOP )?
OOP是一种基于对象概念的编程范式。在计算机科学中,“对象”一词可以指代不同概念,但基本上,它是标识符所引用的内存值(
https://en.wikipedia.org/wiki/Object_(computer_science))。
在OOP的执行环境中,对象指状态(变量)和行为(方法)的组合。面向对象方法的目标是创建可重复使用的软件,以下四个特征使其更易维护:封装、抽象、继承和多态。
还可以进一步区分面向对象语言,例如,基于类和基于原型。
在基于类的OOP中,对象是类的实例。类是关于如何定义某些内容的蓝图,但它不会提供内容本身——它只是提供结构。
学习策略
有许多方法可以用来学习和练习OOP,因必须选择其中一种,所以笔者决定采用莎士比亚的《罗密欧与朱丽叶》的故事线,制作一个简单的基于文本的游戏。具体步骤如下:
1. 写下故事
2. 指出问题
3. 确定实体——这些是类别
4. 创建实体层次结构
5. 确定实体的功能
6. 编写测试
7. 检查测试是否成功——因未编写过任何代码,所以最初会出现错误!
8. 编写代码
9. 重复!重构!精炼!
这个过程不是一成不变的,也不是注定要被它牵着鼻子走。这一系列步骤帮助笔者开始了编程之路。面向对象不仅仅是一种编程范式,还是一种解决问题的方法,尽管它并非没有非议,但在构建复杂系统时却是一个很好的选择。
兔子洞(The Rabbit Hole)
请注意步骤6:编写测试。现在,这个不是笔者最初的步骤。笔者正计划给各类别编码。但在研究OOP时,笔者发现了测试驱动开发( TDD )这一概念。
TDD是一种编程实践,开始于程序每个功能所进行的测试的设计和开发。这样,在开始编写代码之前,你就不得不考虑其规范、要求或设计。换句话说,在编写任何代码之前,都要编写代码来测试代码。
是不是感到迷惑不解?笔者也是。但做这个练习是完全值得的。
测试驱动开发和单元测试
TDD的过程非常简单:
图片来自Kanchan Kulkarni的TDD教程(
https://www.guru99.com/test-driven-development.html)
1. 写出测试
2. 运行测试
3. 写下代码
4. 运行测试
5. 重构代码
6. 重复步骤
在这个例子中,笔者使用单元测试(
http://softwaretestingfundamentals.com/unit-testing/)进行TDD。单元测试是软件测试的第一级,其目的是验证程序中的每个单元是否按设计执行。可以使用不同的框架来执行单元测试。
人们对于TDD持有两种不同观点。就我个人的经历而言,TDD的优点如下:
· 在毫无方向地开始输入代码之前,TDD迫使人们必须考虑要解决的问题。
· 在基于类别的OOP的特殊情况下,它能帮助理解每个类别内容。如职责是什么?必须知道什么?——目标是低耦合和高内聚性时,这变得更加息息相关。
· 尽管一开始它可能会减慢速度,但从长远来看,它通过最小化调试时间来节省时间。
· 它鼓励更好的设计,使代码更容易维护、减少冗余(不写重复代码!),并在需要时安全地重构。
· 它是一个动态文档——只需查看测试,就能理解每个单元应该做什么,如此一来,代码就能自证其明。
罗密欧与朱丽叶——代码与测试
在考虑了游戏的故事性之后,笔者决定采用两个不同的故事线:经典桥段和另类桥段。第一个是人们熟知的罗密欧与朱丽叶,第二个故事对我们来说则较为陌生。
故事安排(创建不同类别的引用)如下:
· 场景:蒙面舞会、阳台、决斗,安排、药剂师、卡普莱特墓和另类结局。该“场景”有两个主要职责,即为玩家描述场景,以及以是非问题提示玩家从而获取信息输入。
· 地图:地图就像一个有限状态机(
https://en.wikipedia.org/wiki/Deterministic_finite_automaton)。它具有有限的状态(场景)、转换函数(从一个场景移动到另一个场景)和开始状态(第一个场景)。
· 故事线:定义两个唯一常量值。
从场景的定义中可以看到,所有场景都有相同的职责,只有它们的内容发生了变化(场景的描述和提示)。这就是为什么要利用继承的概念。这个概念允许定义一个类,该类别从另一个类中继承了所有方法和属性;在这种情况下,不重复写代码至关重要。
class Storyline(Enum):
CLASSIC = "classic"
ALTERNATIVE = "alternative"
对于故事线这一类别,笔者使用了Python’s enumeration type(
https://docs.python.org/3/library/enum.html)或enum。在文档中,它们的定义为“一组绑定到唯一常量值的符号名(成员)。在枚举中,成员可以按标识进行比较,枚举本身可以循环访问。”
接下来,我们有Sence类和MockSence类。测试代码中有两个值得注意的特征:1.使用MockMap类;2.创建TestScene类以测试Scene类。在单元测试阶段,将创建一个Test类为每个类编写测试。
Class Scene(object):
a_map = None
def __init__(self, a_map):
self.a_map = a_map
def get_message(self):
return """
This scene is yet to be initialized
"""
def get_prompt(self):
return """
This scene is yet to be initialized
"""
def enter(self):
self.print_description()
self.prompt_user()
def print_description(self):
print(dedent(self.get_message()))
def prompt_user(self):
input_from_user = input(self.get_prompt()).lower()
if input_from_user == "yes":
self.a_map.advance_scene(Storyline.CLASSIC)
elif input_from_user == "no":
self.a_map.advance_scene(Storyline.ALTERNATIVE)
self.a_map.play()
import unittest
from unittest.mock import patch, mock_open
import sys
import io
from romeo_and_juliet import *
class MockMap(Map):
storyline = None
play_executed = False
def advance_scene(self, a_storyline):
self.storyline = a_storyline
def play(self):
self.play_executed = True
class TestScene(unittest.TestCase):
def test_print_description(self):
a_scene = Scene(Map())
Releasing standard output.
sys.stdout = sys.__stdout__
def test_prompt_user(self):
a_map = MockMap()
a_scene = Scene(a_map)
with patch("builtins.input", return_value = "yes"):
a_scene.prompt_user()
self.assertEqual(a_map.storyline, Storyline.CLASSIC)
with patch("builtins.input", return_value = "no"):
a_scene.prompt_user()
self.assertEqual(a_map.storyline, Storyline.ALTERNATIVE)
self.assertTrue(a_map.play_executed)
最后,来看看Map类和TestMap类。和往常一样,我们会创建一个模拟测试,但在本例中,是为Scene类创建一个MockScene类。
class Map(object):
scenes = None
current_scene = None
def __init__(self):
self.scenes = {
"the_masked_ball": TheMaskedBall(self),
"the_balcony": TheBalcony(self),
"the_duel": TheDuel(self),
"the_arrangement": TheArrangement(self),
"the_apothecary": TheApothecary(self),
"the_capulet_tomb": TheCapuletTomb(self),
"the_alternative_ending": TheAlternativeEnding(self)
}
self.current_scene = self.scenes["the_masked_ball"]
def get_current_scene(self):
return self.current_scene
def play(self):
self.current_scene.enter()
def advance_scene(self, storyline):
if storyline == Storyline.CLASSIC:
if self.current_scene == self.scenes["the_masked_ball"]:
self.current_scene = self.scenes["the_balcony"]
elif self.current_scene == self.scenes["the_balcony"]:
self.current_scene = self.scenes["the_duel"]
elif self.current_scene == self.scenes["the_duel"]:
self.current_scene = self.scenes["the_arrangement"]
elif self.current_scene == self.scenes["the_arrangement"]:
self.current_scene = self.scenes["the_apothecary"]
elif self.current_scene == self.scenes["the_apothecary"]:
self.current_scene = self.scenes["the_capulet_tomb"]
elif self.current_scene == self.scenes["the_capulet_tomb"]:
raise Exception
if storyline == Storyline.ALTERNATIVE:
if self.current_scene == self.scenes["the_masked_ball"]:
self.current_scene = self.scenes["the_alternative_ending"]
elif self.current_scene == self.scenes["the_balcony"]:
self.current_scene = self.scenes["the_alternative_ending"]
elif self.current_scene == self.scenes["the_duel"]:
self.current_scene = self.scenes["the_alternative_ending"]
elif self.current_scene == self.scenes["the_arrangement"]:
self.current_scene = self.scenes["the_alternative_ending"]
elif self.current_scene == self.scenes["the_apothecary"]:
self.current_scene = self.scenes["the_alternative_ending"]
elif self.current_scene == self.scenes["the_alternative_ending"]:
raise Exception
class MockScene(Scene):
was_entered = False
def enter(self):
self.was_entered = True
class TestMap(unittest.TestCase):
def test_play(self):
a_map = Map()
mock_scene = MockScene(a_map)
a_map.current_scene = mock_scene
a_map.play()
self.assertTrue(mock_scene.was_entered)
def test_initial_state(self):
a_map = Map()
self.assertIsInstance(a_map.get_current_scene(), TheMaskedBall)
def test_advance_scene_classic(self):
a_map = Map()
self.assertIsInstance(a_map.get_current_scene(), TheMaskedBall)
a_map.advance_scene(Storyline.CLASSIC)
self.assertIsInstance(a_map.get_current_scene(), TheBalcony)
a_map.advance_scene(Storyline.CLASSIC)
self.assertIsInstance(a_map.get_current_scene(), TheDuel)
a_map.advance_scene(Storyline.CLASSIC)
self.assertIsInstance(a_map.get_current_scene(), TheArrangement)
a_map.advance_scene(Storyline.CLASSIC)
self.assertIsInstance(a_map.get_current_scene(), TheApothecary)
a_map.advance_scene(Storyline.CLASSIC)
self.assertIsInstance(a_map.get_current_scene(), TheCapuletTomb)
with self.assertRaises(Exception):
a_map.advance_scene(Storyline.CLASSIC)
def test_advance_scene_alternative_one(self):
a_map = Map()
self.assertIsInstance(a_map.get_current_scene(), TheMaskedBall)
a_map.advance_scene(Storyline.ALTERNATIVE)
self.assertIsInstance(a_map.get_current_scene(), TheAlternativeEnding)
with self.assertRaises(Exception):
a_map.advance_scene(Storyline.ALTERNATIVE)
def test_advance_scene_alternative_two(self):
a_map = Map()
self.assertIsInstance(a_map.get_current_scene(), TheMaskedBall)
a_map.advance_scene(Storyline.CLASSIC)
self.assertIsInstance(a_map.get_current_scene(), TheBalcony)
a_map.advance_scene(Storyline.ALTERNATIVE)
self.assertIsInstance(a_map.get_current_scene(), TheAlternativeEnding)
with self.assertRaises(Exception):
a_map.advance_scene(Storyline.ALTERNATIVE)
def test_advance_scene_alternative_three(self):
a_map = Map()
self.assertIsInstance(a_map.get_current_scene(), TheMaskedBall)
a_map.advance_scene(Storyline.CLASSIC)
self.assertIsInstance(a_map.get_current_scene(), TheBalcony)
a_map.advance_scene(Storyline.CLASSIC)
self.assertIsInstance(a_map.get_current_scene(), TheDuel)
a_map.advance_scene(Storyline.ALTERNATIVE)
self.assertIsInstance(a_map.get_current_scene(), TheAlternativeEnding)
with self.assertRaises(Exception):
a_map.advance_scene(Storyline.ALTERNATIVE)
def test_advance_scene_alternative_four(self):
a_map = Map()
self.assertIsInstance(a_map.get_current_scene(), TheMaskedBall)
a_map.advance_scene(Storyline.CLASSIC)
self.assertIsInstance(a_map.get_current_scene(), TheBalcony)
a_map.advance_scene(Storyline.CLASSIC)
self.assertIsInstance(a_map.get_current_scene(), TheDuel)
a_map.advance_scene(Storyline.CLASSIC)
self.assertIsInstance(a_map.get_current_scene(), TheArrangement)
a_map.advance_scene(Storyline.ALTERNATIVE)
self.assertIsInstance(a_map.get_current_scene(), TheAlternativeEnding)
with self.assertRaises(Exception):
a_map.advance_scene(Storyline.ALTERNATIVE)
def test_advance_scene_alternative_five(self):
a_map = Map()
self.assertIsInstance(a_map.get_current_scene(), TheMaskedBall)
a_map.advance_scene(Storyline.CLASSIC)
self.assertIsInstance(a_map.get_current_scene(), TheBalcony)
a_map.advance_scene(Storyline.CLASSIC)
self.assertIsInstance(a_map.get_current_scene(), TheDuel)
a_map.advance_scene(Storyline.CLASSIC)
self.assertIsInstance(a_map.get_current_scene(), TheArrangement)
a_map.advance_scene(Storyline.CLASSIC)
self.assertIsInstance(a_map.get_current_scene(), TheApothecary)
a_map.advance_scene(Storyline.ALTERNATIVE)
self.assertIsInstance(a_map.get_current_scene(), TheAlternativeEnding)
with self.assertRaises(Exception):
a_map.advance_scene(Storyline.ALTERNATIVE)
留言 点赞 关注
我们一起分享AI学习与发展的干货
编译组:余书敏、张璐瑶
相关链接:
https://towardsdatascience.com/object-oriented-programming-and-the-magic-of-test-driven-development-d377acae85fa
如需转载,请后台留言,遵守转载规范