"
]
},
{
"cell_type": "code",
"execution_count": 1,
"metadata": {},
"outputs": [
{
"data": {
"text/html": [
"\n",
"\n"
],
"text/plain": [
""
]
},
"execution_count": 1,
"metadata": {},
"output_type": "execute_result"
}
],
"source": [
"# code for loading the format for the notebook\n",
"import os\n",
"\n",
"# path : store the current path to convert back to it later\n",
"path = os.getcwd()\n",
"os.chdir(os.path.join('..', 'notebook_format'))\n",
"\n",
"from formats import load_style\n",
"load_style(plot_style=False)"
]
},
{
"cell_type": "code",
"execution_count": 2,
"metadata": {},
"outputs": [
{
"name": "stdout",
"output_type": "stream",
"text": [
"Ethen 2019-02-23 12:06:38 \n",
"\n",
"CPython 3.6.4\n",
"IPython 6.4.0\n"
]
}
],
"source": [
"os.chdir(path)\n",
"\n",
"# 1. magic to print version\n",
"# 2. magic so that the notebook will reload external python modules\n",
"%load_ext watermark\n",
"%load_ext autoreload\n",
"%autoreload 2\n",
"\n",
"import json\n",
"import xml.etree.ElementTree as et\n",
"\n",
"%watermark -a 'Ethen' -d -t -v"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"# Factory Design Pattern"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"Let's look at an example where we need to convert the Song object to a string representation according to a user-specified format parameter."
]
},
{
"cell_type": "code",
"execution_count": 3,
"metadata": {},
"outputs": [],
"source": [
"class Song:\n",
" \"\"\"\n",
" By default Python uses a dict (__dict__) to store an object’s instance attributes.\n",
" This is really helpful as it allows the user to set arbitrary new attributes at runtime.\n",
" However, for small classes with known attributes it might be a bottleneck as dict wastes a lot of RAM\n",
" due to the fact that Python can’t just allocate a static amount of memory at object creation to store\n",
" all the attributes. Therefore it sucks a lot of RAM if we create a lot of objects. One way to circumvent\n",
" this issue involves the usage of __slots__ to tell Python not to use a dict and only allocate space for\n",
" a fixed set of attributes. By adding it we no longer have the ability to add new attributes to\n",
" the class at run time.\n",
" \n",
" http://book.pythontips.com/en/latest/__slots__magic.html\n",
" \"\"\"\n",
" __slots__ = ['song_id', 'title', 'artist']\n",
"\n",
" def __init__(self, song_id, title, artist):\n",
" self.song_id = song_id\n",
" self.title = title\n",
" self.artist = artist\n",
"\n",
"\n",
"def serialize_song(song, format):\n",
" if format == 'JSON':\n",
" song_info = {\n",
" 'id': song.song_id,\n",
" 'title': song.title,\n",
" 'artist': song.artist\n",
" }\n",
" return json.dumps(song_info)\n",
" elif format == 'XML':\n",
" song_info = et.Element('song', attrib={'id': song.song_id})\n",
" title = et.SubElement(song_info, 'title')\n",
" title.text = song.title\n",
" artist = et.SubElement(song_info, 'artist')\n",
" artist.text = song.artist\n",
" return et.tostring(song_info, encoding='unicode')\n",
" else:\n",
" raise ValueError(format)"
]
},
{
"cell_type": "code",
"execution_count": 4,
"metadata": {},
"outputs": [
{
"name": "stdout",
"output_type": "stream",
"text": [
"{\"id\": \"1\", \"title\": \"Water of Love\", \"artist\": \"Dire Straits\"}\n",
"Water of LoveDire Straits\n"
]
}
],
"source": [
"song = Song('1', 'Water of Love', 'Dire Straits')\n",
"print(serialize_song(song, 'JSON'))\n",
"print(serialize_song(song, 'XML'))"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"The code above works fine but can benefit from refactoring. [One of the best practices behind writing clean code is Single Responsibility Principle](https://medium.com/@george.seif94/these-5-clean-code-tips-will-dramatically-improve-your-productivity-b20c152783b). Here, instead of using a complex if/elif/else conditional structure to determine the concrete implementation, the application delegates that decision to a separate component that creates the concrete object. Then concrete implementation of the interface is identified by some parameter. With this approach, we can have a class/method that does one thing only and one thing well, making it more reusable and easier to maintain.\n",
"\n",
"This type of creational design pattern is so called Factory Design Pattern.\n",
"\n",
"Let's take a look at how we can refactor the code above. The first step when we see complex conditional code in an application is to identify the common goal of each of the execution paths (or logical paths), and separate out implementations for each logical path. With the factory pattern, we let the client, our `serialize` method depend on a creator, `get_serializer` method, which returns the actual implementation using some sort of identifier."
]
},
{
"cell_type": "code",
"execution_count": 5,
"metadata": {},
"outputs": [],
"source": [
"def serialize_song(song, format):\n",
" serializer = get_serializer(format)\n",
" return serializer(song)\n",
"\n",
"\n",
"def get_serializer(format):\n",
" if format == 'JSON':\n",
" return _serialize_to_json\n",
" elif format == 'XML':\n",
" return _serialize_to_xml\n",
" else:\n",
" raise ValueError(format)\n",
"\n",
"\n",
"def _serialize_to_json(song):\n",
" payload = {\n",
" 'id': song.song_id,\n",
" 'title': song.title,\n",
" 'artist': song.artist\n",
" }\n",
" return json.dumps(payload)\n",
"\n",
"\n",
"def _serialize_to_xml(song):\n",
" song_element = et.Element('song', attrib={'id': song.song_id})\n",
" title = et.SubElement(song_element, 'title')\n",
" title.text = song.title\n",
" artist = et.SubElement(song_element, 'artist')\n",
" artist.text = song.artist\n",
" return et.tostring(song_element, encoding='unicode')"
]
},
{
"cell_type": "code",
"execution_count": 6,
"metadata": {},
"outputs": [
{
"data": {
"text/plain": [
"'{\"id\": \"1\", \"title\": \"Water of Love\", \"artist\": \"Dire Straits\"}'"
]
},
"execution_count": 6,
"metadata": {},
"output_type": "execute_result"
}
],
"source": [
"song = Song('1', 'Water of Love', 'Dire Straits')\n",
"serialize_song(song, 'JSON')"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"# Reference"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"- [Blog: The Factory Method Pattern and Its Implementation in Python](https://realpython.com/factory-method-python/)\n",
"- [Github: A collection of design patterns/idioms in Python - Factory](https://github.com/faif/python-patterns/blob/master/patterns/creational/factory_method.py)"
]
}
],
"metadata": {
"kernelspec": {
"display_name": "Python 3",
"language": "python",
"name": "python3"
},
"language_info": {
"codemirror_mode": {
"name": "ipython",
"version": 3
},
"file_extension": ".py",
"mimetype": "text/x-python",
"name": "python",
"nbconvert_exporter": "python",
"pygments_lexer": "ipython3",
"version": "3.6.4"
},
"toc": {
"nav_menu": {},
"number_sections": true,
"sideBar": true,
"skip_h1_title": false,
"title_cell": "Table of Contents",
"title_sidebar": "Contents",
"toc_cell": true,
"toc_position": {
"height": "calc(100% - 180px)",
"left": "10px",
"top": "150px",
"width": "273px"
},
"toc_section_display": true,
"toc_window_display": true
},
"varInspector": {
"cols": {
"lenName": 16,
"lenType": 16,
"lenVar": 40
},
"kernels_config": {
"python": {
"delete_cmd_postfix": "",
"delete_cmd_prefix": "del ",
"library": "var_list.py",
"varRefreshCmd": "print(var_dic_list())"
},
"r": {
"delete_cmd_postfix": ") ",
"delete_cmd_prefix": "rm(",
"library": "var_list.r",
"varRefreshCmd": "cat(var_dic_list()) "
}
},
"types_to_exclude": [
"module",
"function",
"builtin_function_or_method",
"instance",
"_Feature"
],
"window_display": false
}
},
"nbformat": 4,
"nbformat_minor": 2
}