본문 바로가기
James Cooper - Python Programming with D

The Adapter Pattern

by 자동매매 2023. 3. 23.

12**

The Adapter Pattern

The Adapter pattern is used to convert the programming interface of one class into that of another. We use adapters whenever we want unrelated classes to work together in a single program. The concept of an adapter is thus pretty simple; we write a class that has the desired interface and then make it communicate with the class that has a different interface.

There are two ways to do this: by inheritance, and by object composition. With inheritance, we derive a new class from the nonconforming one and add the methods we need to make the new derived class match the desired interface. The other way is to include the original class inside the new one and create the methods to translate calls within the new class. These two approaches, termed class adapters and object adapters, are both fairly easy to implement in Python.

Moving Data Between Lists

Let’s consider a simple Python program that enables you to enter student names into a list and then select some of those names to be transferred to another list. The initial list consists of a class roster. The second list contains students who will be doing advanced work, as shown in Figure 12- 1.

Figure 12-1 Student names app

In this simple program, you enter names into the top entry field and click Insert to move the names into the listbox on the left. To move a name to the listbox on the right, you click the name and then click Add. To remove a name from the listbox on the right, click the name and then click Remove; this moves the name back to the list on the left.

This is a very simple program to write in Python. It consists of a GUI creation constructor and three DButtons, each with their own comd method. Because you perform the same operations on the two listboxes, you create a derived Listbox class with those operations built in:

Click here to view code image

  • Derived Listbox with 3 convenience methods class DListbox(Listbox):

def __init__(self, root):

super().__init__(root)

  • Get the current selected text

def getSelected(self): selection = self.curselection() selindex = selection[0]

return self.get(selindex)

  • delete the selected row

def deleteSelection(self): selection = self.curselection() selindex = selection[0]

self.delete(selindex)

  • Insert at bottom of list

def insertAtEnd(self, text):

self.insert(END, text)

The Entry button copies the entry field into the bottom of the left list and then clears the entry field:

Click here to view code image

class EntryButton(DButton):

def __init__(self, root, buildui, **kwargs):

super().__init__(root, text="Enter") self.buildui = buildui

  • copies entry field into left list

def comd(self):

entry = self.buildui.getEntry()

text = entry.get()

leftList = self.buildui.getLeftList() leftList.insertAtEnd(text) entry.delete(0, END) # clears entry

The Move button copies the selected list item into the right list and deletes it from the left:

Click here to view code image

class MoveButton(DButton):

def __init__(self, root, buildui, **kwargs):

super().__init__(root, text="Move-->") self.buildui = buildui

  • copies selected line into right list

def comd(self):

self.leftlist =self.buildui.getLeftList() self.seltext = self.leftlist.getSelected() self.rightlist = self.buildui.getRightList()

self.rightlist.insertAtEnd(self.seltext)

  • and deletes from left self.leftlist.deleteSelection()

This program is called addStudents.py and is on the website.

Making an Adapter

Suppose that we want a different display on the right side. Maybe we want a table of students that includes more data, such as their IQ or test scores. This is likely a job for a table. Fortunately, the Treeview widget can do what we want (see Figure 12-2).

Figure 12-2 Student names with Treeview

We still have to create the Treeview columns in our UI builder class, but let’s say we prefer not to change the listbox interfaces. To be more specific, we’d like to be able to use the same methods as we showed in the DListbox class illustrated at the beginning of this chapter.

So, we want to build an Adapter class that has those same methods but that interfaces with the Treeview widget.

Click here to view code image

class ListboxAdapter(DListbox):

def __init__(self, root, tree):

super().__init__(root) self.tree = tree self.index=1

  • gets the text selected from the tree

def getSelected(self):

treerow = self.tree.focus() #get the row row = self.tree.item(treerow) # returns dict

return row.get('text')

  • delete the line selected in the tree def deleteSelection(self):

treerow = self.tree.focus()

self.tree.delete(treerow)

  • insert a line at the bottom of the treelist

def insertAtEnd(self, name):

  • create random IQs and scores self.tree.insert("", self.index, text=name, values=(Randint.getIQ(self),

Randint.getScore(self)) )

self.index += 1

The getSelected method uses the obscurely named focus() method to get

the selected row. That method returns a key to the selected row. Then we use the item() method, which returns a dictionary of the elements in that row. Then the text element of that dictionary is the name of the student:

Click here to view code image

treerow = self.tree.focus() #get the row row = self.tree.item(treerow) # returns dict

return row.get('text')

We can finesse the issue of keeping the student IQs and score somewhere and just generate them using a random number generator, which can be used to calculate integers in a predefined range:

Click here to view code image

  • Random number generator

class Randint():

def __init__(self):

seed(None, 2) # set up the random seed

  • compute a random IQ between 115 and 145 @staticmethod

def getIQ(self):

return randint(115,145)

  • compute a random score between 25 and 35 @staticmethod

def getScore(self):

return randint(25,35)

The Class Adapter

The previous example is an object adapter that operates on an instance of the Treelist inside the adapter. By contrast, a class adapter derives a new class from the Treelist that has the methods you need. This is very simple to achieve, and the code differs very little between the two.

  • The Class adapter
    • Doesn’t work when you want to adapt a class and all its subclasses because you define the class it derives from when you create it
    • Lets the adapter change some of the adapted class’s methods, but still allows the others to be used unchanged
    • An Object adapter
      • Can allow subclasses to be adapted by simply passing them in as part of a constructor
      • Requires that you specifically bring to the surface any of the adapted object’s methods that you want to make available

Two-Way Adapters

The two-way adapter is a clever concept that enables an object to be viewed by different classes as being of type Listbox or type Treelist. This is most easily carried out using a class adapter because all the methods of the base class are automatically available to the derived class. However, this works only if you do not override any of the base class’s methods with ones that behave differently. As it happens, the ListboxAdapter class here is an ideal two-way adapter because the two classes have no methods in common.

Pluggable Adapters

A pluggable adapter is one that adapts dynamically to one of several classes. Of course, the adapter can adapt only to classes that it can recognize. Usually, the adapter decides which class it is adapting based on differing constructors or setParameter methods.

Programs on GitHub

  • addStudents.py: Adds students to the left list and can move some to the right list.
  • addStudentsAdapter.py: Adds students to the left list and can move some to the Treeview on the right using an Adapter.

13**

The Bridge Pattern

At first sight, the Bridge pattern looks much like the Adapter pattern in that a class is used to convert one kind of interface to another. However, the intent of the Adapter pattern is to make one or more classes’ interfaces look the same as the interface of a particular class. The Bridge pattern is designed to separate a class’s interface from its implementation so that you can vary or replace the implementation without changing the client code.

Suppose that we have a program that displays a list of products in a window. The simplest interface for that display is a simple Listbox. But after a significant number of products have been sold, we may want to display the products in a table along with their sales figures.

We have just discussed the Adapter pattern, so you might think immediately of the class-based adapter, where we adapt the interface of the Listbox to our simpler needs in this display. In simple programs, this works fine, but as we’ll see below, there are limits to that approach.

Let’s further suppose that we need to produce two kinds of displays from our product data: a customer view, which is just the list of products we’ve already mentioned, and an executive view, which also shows the number of units shipped. We’ll display the product list in an ordinary Listbox and display the executive view in a Treeview display (see Figure 13-1). These two displays are the implementations of the display classes.

Figure 13-1 Parts list with Treeview

Now, we want to define a single simple interface that remains the same, regardless of the type and complexity of the actual implementation classes. We’ll start by defining an abstract Bridger class:

Click here to view code image

class Bridger(Frame):

def addData(self):pass

This class is so simple that it just receives a List of data and passes it on to the display classes.

On the other side of the bridge are the implementation classes, which usually have a more elaborate and somewhat lower-level interface. Here we’ll have them add the data lines to the display one at a time.

Click here to view code image

class VisList():

def addLines(self): pass def removeLine(self): pass

Implicit in the definition of these classes is some mechanism for determining which part of each string is the name of the product and which part is the quantity shipped. In this simple example, we separate the quantity from the name with two dashes and parse these apart in a Product class.

The Bridge between the interface on the left and the implementation on the right is the ListBridge class, which instantiates one or the other of the list display classes. Note that it extends the Bridger class for use of the application program.

Click here to view code image

  • General bridge between data and any VisList class class ListBridge(Bridger):

def __init__(self, frame, vislist):

self.list = vislist self.list.pack()

  • adds the list of Products into any VisList

def addData(self, products):

self.list.addLines( products)

In the current example, we use the Bridge class twice: once to display the Listbox on the left side and once to display the Treeview table on the right side.

The power and simplicity of the Bridge pattern becomes obvious when you realize that you can completely change the display by replacing either or both of the two VisList classes that display the data. You don’t have to change the Bridge class code: Just give it new VisLists to display. Those classes can be anything, as long as they implement the simple VisList methods. In fact, we left the removeLine method empty here because it isn’t really relevant to this example.

The VisList for Listbox is quite simple: Click here to view code image

  • Listbox visual list

class LbVisList(Listbox, VisList): def __init__(self, frame ):

super().__init__(frame)

def addLines(self, prodlist):

for prod in prodlist:

self.insert(END, prod.name)

The Treeview table on the right is equally simple, except for setting up the column names and dimensions:

Click here to view code image

  • Treelist (table) visual list

class TbVisList(Treeview, VisList) :

def __init__(self, frame ):

super().__init__(frame)

  • set up table columns

self["columns"] = ("quantity")

self.column("#0", width=150, minwidth=100, stretch=NO)

self.column("quantity", width=50, minwidth=50, stretch=NO) self.heading('#0', text='Part')

self.heading('quantity', text='Qty'

self.index = 0 #row counter

  • adds the whole list of products to the table

def addLines(self, prodlist):

for prod in prodlist:

self.insert("", self.index, text=prod.name,

values=(prod.count)) self.index += 1

Creating the User Interface

Although all the usual grid and pack layout code still applies, creating the two frames inside a two-member grid is really easy. Coding involves creating the frame and the VisList, creating the Bridge, and adding the data:

Click here to view code image

self.vislist = LbVisList(self.lframe) self.lbridge = ListBridge(self.lframe, self.vislist)

self.lbridge.addData(prod.getProducts())

Similarly, you create the Treeview version of a VisList, add it to another instance of the Bridge, and add the data:

Click here to view code image

self.rvislist = TbVisList(self.rframe) self.rlb = ListBridge( self.rframe, self.rvislist) self.rlb.addData(prod.getProducts())

Extending the Bridge

The power of the Bridge becomes apparent when we need to make some changes in the way these lists display the data. For example, you might want to have the products displayed in alphabetical order. You might think you’d need to either modify or subclass both the list and table classes. This can quickly get to be a maintenance nightmare, especially if more than two such displays eventually are needed. Instead, we simply make the changes in the extended interface class, creating a new sortBridge class from the parent listBridge class. However, you only need to create a new VisList

that sorts the data and installs it instead of the original LbVislist class.

Click here to view code image

  • Sorted listbox visual list

class SortVisList(Listbox, VisList): def __init__(self, frame ):

super().__init__(frame)

  • sort into alphabetical order

def addLines(self, prodlist):

  • sort array alphabetically self.prods = self.sortUpwards( prodlist) for prod in self.prods: self.insert(END, prod.name)

The sorting routine is the same one used in the Swimfactory example, so we won’t repeat it here. Figure 13-2 shows the resulting sorted display.

Figure 13-2 Sorted VisList

This clearly shows that you can vary the interface without changing the implementation. The converse is also true. For example, you can create another type of list display and replace one of the current list displays without any other program changes, as long as the new list also implements the visList interface.

In the next example, we have created a Treeview component that implements the visList interface and replaces the ordinary list without any change in the public interface to the classes in Figure 13-3.

Figure 13-3 Treeview as left VisList

Note that this simple new VisList is the only change in the code: Click here to view code image

#Tree VisList for left display

class TbexpVisList(Treeview, VisList) :

def __init__(self, frame ):

super().__init__(frame)

self.column("#0", width=150, minwidth=100,

stretch=NO)

self.index = 0

def addLines(self, prodlist):

for prod in prodlist: fline = self.insert("", self.index,

text=prod.name)

  • add count as a leaf self.insert(fline, 'end', text=prod.count )

self.index += 1

Consequences of the Bridge Pattern

Consequences of the Bridge pattern include the following:

  1. The Bridge pattern is intended to keep the interface to your client program constant while enabling you to change the actual kind of class you display or use. This can prevent you from recompiling a complicated set of user interface modules and require only that you recompile the bridge itself and the actual end display class.
  2. You can extend the implementation class and the bridge class separately, usually without much interaction with each other.
  3. You can hide implementation details from the client program much more easily.

Programs on GitHub

In all these samples, be sure to include the data file (products.txt) in the same folder as the Python file. Also make sure they are part of the project in Vscode or PyCharm.

  • BasicBridge.py
    • SortBridge.py
      • TreeBridge.py
        • Products.txt: Data file for Bridge programs

14**

The Composite Pattern

Programmers frequently develop systems in which a component may be an individual object, or it may represent a collection of objects. The Composite pattern is designed to accommodate both cases. You can use the Composite pattern to build part-whole hierarchies or to construct data representations of trees. In summary, a composite is a collection of objects, any one of which may be either a composite or just a primitive object. In tree nomenclature, some objects may be nodes with additional branches and some may be leaves.

The problem that develops is the dichotomy between having a single simple interface to access all the objects in a composite and having the capability to distinguish between nodes and leaves. Nodes have children and can have children added to them. Leaves, on the other hand, do not at the moment have children and, in some implementations, may be prevented from having children added to them.

In considering a tree of employees, some authors have suggested creating a separate interface for nodes and leaves, where a leaf could have these methods:

Click here to view code image

def getName(self):pass def getSalary(self):pass

A node could have the additional methods: Click here to view code image

def getSubordinates(self):pass def add(self, e:Employee):pass def getChild(self, name:str):

This leaves us with the programming problem of deciding which elements will be which when we construct the composite. However, Design Patterns suggests that each element should have the same interface, whether it is a composite or a primitive element. This is easier to accomplish, but we are left with the question of what the getChild() operation should accomplish when the object is actually a leaf.

Just as difficult is the issue of adding or removing leaves from elements of the composite. A nonleaf node can have child leaves added to it, but a leaf node cannot. However, here you want all components in the composite to have the same interface. Attempts to add children to a leaf node must not be allowed, and we might design the leaf node class to throw an exception if the program attempts to add to such a node.

An Implementation of a Composite

Let’s consider a small company. Suppose that it started with a single person, the CEO, who got the business going. Then the CEO hired a couple of people to handle the marketing and manufacturing. Soon each of those people hired additional assistants to help with advertising, shipping, and so forth, and they became the company’s first two vice presidents. As the company’s success continued, the firm continued to grow until it had the organizational chart in Figure 14-1.

Figure 14-1 Org chart for a Composite

Salary Computation

Now, if the company is successful, each of these company members receives a salary, and at any time, we could ask for the cost of any employee to the company. Here we define the cost as the salary of that person and those of all his or her subordinates. This is an ideal example for a composite:

  • The cost of an individual employee is simply his or her salary (and benefits).
  • The cost of an employee who heads a department is his or her salary plus the salaries of all their subordinates.

We would want a single interface that produces the salary totals correctly, regardless of whether the employee has subordinates or not.

def getSalaries(self):pass

At this point, we realize that the idea of all Composites having the same standard method names in their interface is probably naïve. We’d prefer that the public methods be related to the kind of class we are actually developing. So instead of having generic methods such as getValue(), we

use getSalaries().

The Employee Classes

Now imagine representing the company as a Composite made up of nodes: managers and employees. It would be possible to use a single class to represent all employees, but because each level may have different properties, it might be more useful to define at least two classes: Employees and Bosses. Employees are leaf nodes and cannot have employees under

them. Bosses are nodes that may have employee nodes under them.

Our concrete Employee class can store the name and salary of each employee and enables us to fetch them as needed.

Click here to view code image

  • Employee is the base class

class Employee():

def __init__(self, parent, name, salary:int):

self.parent = parent

self.name = name

self.salary = salary

self.isleaf = True

def getSalaries(self): return self.salary

def getSubordinates(self): return None

The Employee class can have concrete implementations of the add, getSubordinates, and getChild methods. But because an Employee is a leaf, all of these could return some sort of error indication. For example, getSubordinates could return None as above, but because an Employee is always a leaf, you could avoid calling these methods on leaf nodes.

The Boss Class

The Boss class is a subclass of Employee and enables us to store subordinate employees as well. We’ll store them in a List called subordinates and return them as a list or just enumerate through the list. Thus, if a particular Boss has temporarily run out of Employees, the List will just be empty.

Click here to view code image

class Boss(Employee):

def __init__(self, name, salary:int):

super().__init__(name, salary) self.subordinates = [] self.isleaf = False

def add(self, e:Employee):

self.subordinates.append(e)

Similarly, you can use this same List to return a sum of salaries for any employee and his or her subordinates:

Click here to view code image

  • called recursively as it walks down the tree def getSalaries(self):

self.sum = self.salary

for e in self.subordinates:

self.sum = self.sum + e.getSalaries() return self.sum

Note that this method starts with the salary of the current Employee and then calls the getSalaries() method on each subordinate. This, of course, is recursive; any employees who have subordinates will be included.

Building the Employee Tree

We start by creating a CEO Employee and then add his or her subordinates and then their subordinates, as follows:

Click here to view code image

#builds the employee tree

def build(self):

seed(None, 2) # initialize random seed boss = Boss("CEO", 200000)

  • add VPs under Boss

marketVP = Boss("Marketing_VP", 100000) boss.add(marketVP)

prodVP = Boss("Production_VP", 100000)

boss.add(prodVP)

salesMgr = Boss("Sales_Mgr", 50000) marketVP.add(salesMgr)

advMgr = Boss("Advt_Mgr", 50000) marketVP.add(advMgr)

  • add salesmen reporting to Sales Mgr

for i in range(0, 6): salesMgr.add(Employee("Sales_" + str(i), int(30000.0 + random() * 10000)))

advMgr.add(Employee( prodMgr = Boss( prodVP.add(prodMgr) shipMgr = Boss( prodVP.add(shipMgr)

"Secy", 20000)) "Prod_Mgr", 40000)

"Ship_Mgr", 35000)

  • Add manufacturing and shipping employees

for i in range(0, 4): prodMgr.add(Employee("Manuf_"

  • str(i), int(25000 + random() * 5000)) for i in range(0, 4):

shipMgr.add(Employee("Ship_Clrk_"

  • str(i), int(20000 + random() * 5000)))

Printing the Employee Tree

You don’t really need to create a graphic interface to print this tree. You just indent two spaces for each new sublevel. This simple recursive code walks down the tree and indents as needed.

Click here to view code image

  • print employee tree recursively,
  • walking down the tree

def addNodes(self, emp:Employee ):

if not emp.isleaf: #Bosses are not Leaves empList = emp.getSubordinates()

if empList != None: # must be a Boss

for newEmp in empList:

print(" "*self.indent, newEmp.name, newEmp.salary)

self.indent += 2

self.addNodes(newEmp)

self.indent-=2

The resulting employee list follows:

CEO 200000

Marketing_VP 100000 Sales_Mgr 50000

Sales_0 39023

Sales_1 36485

Sales_2 35844

Sales_3 32353

Sales_4 32080

Sales_5 33285

Advt_Mgr 50000

Secy 20000

Production_VP 100000 Prod_Mgr 40000

Manuf_0 26536

Manuf_1 29837

Manuf_2 28931

Manuf_3 28509

Ship_Mgr 35000

Ship_Clrk_0 20856 Ship_Clrk_1 20552 Ship_Clrk_2 20476

Ship_Clrk_3 21465

If you want to get the salary span for an employee, you can easily compute it using a Salary class:

Click here to view code image

#compute salaries under selected employee

class SalarySpan():

def __init__(self, boss, name):

self.boss = boss

self.name = name

  • print sum of salaries
  • for employee and subordinates

def print(self):

#search for match

if self.name == self.boss.name: print(self.name, self.boss.name)

newEmp = self.boss

else:

newEmp = self.boss.getChild(self.name) sum = newEmp.getSalaries() # sum salaries

print('Salary span for '+self.name, sum)

We provide the simple question at the end of the tree printout: Click here to view code image

Enter employee name for salary span (q for quit): Ship_Mgr Salary span for Ship_Mgr 120963

Note that these values vary every time you run the program because some of the salaries are computed using a random number generator.

Creating a Treeview of the Composite

After we have constructed this Composite structure, we could also load a Treeview by starting at the top node and calling the addNode() method recursively until all the leaves in each node are accessed—much as we did above in the console version, but loading a Treeview element each time.

Click here to view code image

  • builds Treeview recursively, walking down the tree def addNodes(self, pnode, emp:Employee ):

if not emp.isleaf: # Bosses are not Leaves empList = emp.subordinates

if empList != None: # must be a Boss

for newEmp in empList:

newnode = Tree.tree.insert(pnode, Tree.index,

text = newEmp.name) self.addNodes(newnode, newEmp)

Figure 14-2 shows the final program display.

Figure 14-2 Final Employee tree

The Salaries button computes the sum of all the salaries for the CEO on down or from any Employee you click.

This simple computation calls the getChild() method recursively to obtain all the subordinates of that Employee. Note that we use the comma format string to insert the commas in the salary.

Click here to view code image

#click here to compute salaries for an employee class SalaryButton(DButton):

def __init__(self, master, boss, entry, **kwargs):

super().__init__(master, text="Salaries") self.boss = boss

self.entry = entry

def comd(self): curitem = Tree.tree.focus() # get item dict= Tree.tree.item(curitem) name= dict["text"] # get name

  • search for match

if name == self.boss.name: print(name, self.boss.name)

newEmp = self.boss

else:

newEmp = self.boss.getChild(name) sum = newEmp.getSalaries()

  • put salary sum in entry field self.entry.delete(0, "end") self.entry.insert(0, f'{sum:,}')

Using Doubly Linked Lists

In the previous implementation, we keep a reference to each subordinate in the List in each Boss class. This means that you can move down the chain from the president to any employee, but there is no way to move back up to find out who an employee’s supervisor is. This is easily remedied by providing a constructor for each Employee subclass that includes a reference to the parent node:

Click here to view code image

class Employee():

def __init__(self, parent, name, salary:int):

self.parent = parent

self.name = name

self.salary = salary self.isleaf = True

Then you can quickly walk up the tree to produce a reporting chain: Click here to view code image

emp = Tree.findMatch( quit = False

mesg = ""

while not quit:

mesg += (emp.name + emp = emp.parent

quit = emp.name == mesg += (emp.name + messagebox.showinfo(

self, self.boss)

"\n") "CEO"

"\n")

"Report chain", mesg)

as shown in Figure 14-3.

Figure 14-3 Chain of command display

Consequences of the Composite Pattern

The Composite pattern allows you to define a class hierarchy of simple objects and more complex composite objects so that they appear to be the same to the client program. Because of this simplicity, the client can be that much simpler because nodes and leaves are handled in the same way.

The Composite pattern also makes it easy for you to add new kinds of components to your collection, as long as they support a similar programming interface. On the other hand, this has the disadvantage of making your system overly general. You might find it harder to restrict certain classes, where this would normally be desirable.

A Simple Composite

The intent of the Composite pattern is to allow you to construct a tree of various related classes, even though some have different properties than others and some are leaves that do not have children. However, for very simple cases, you can sometimes use just a single class that exhibits both parent and leaf behavior. In the SimpleComposite example, we create an Employee class that always contains the List employees. This List of employees will either be empty or populated, which determines the nature of the values that you return from the getChild and remove methods. In this simple case, you do not throw exceptions, and you always allow leaf nodes to be promoted to have child nodes. In other words, you always allow execution of the add method.

While you may not regard this automatic promotion as a disadvantage, in systems where there are a very large number of leaves, it is wasteful to keep a List initialized and unused in each leaf node. In cases where there are relatively few leaf nodes, this is not a serious problem.

Other Implementation Issues

Other implementation issues include recursive calls, ordering components, and caching results.

Dealing with Recursive Calls

Both the Boss and Employee classes search the employee subordinate list recursively, meaning that the getSalaries method inside the Boss class calls itself to walk down the employee tree. This means that each new call is a new instance of the Boss class, so there would be new instance variables. Therefore, a reference to the Treeview cannot be kept inside the Boss class.

We solved this by creating a static Tree class that contains the reference to the Treeview that can be referred to as Tree.tree. This is initialized when the UI is created. We also searched down the Employee tree using the selected item in the Treeview display and then put that search in the Tree class as well:

Click here to view code image

class Tree():

tree = None # static variable index=0

column=0

  • searches for the node that matches the
  • selected Treeview item.

def findMatch(self,boss): curitem = Tree.tree.focus() # get selected dict = Tree.tree.item(curitem) name = dict["text"] # get name

  • search for match

if name == boss.name:

print(name, boss.name)

newEmp = boss

else:

newEmp = self.boss.getChild(name)

return newEmp

Ordering Components

In some programs, the order of the components may be important. If that order is somehow different from the order in which they were added to the parent, then the parent must do additional work to return them in the correct order. For example, you might sort the original List alphabetically and return the iterator to a new sorted list.

Caching Results

If you frequently ask for data that must be computed from a series of child components, as we did here with salaries, it may be advantageous to cache these computed results in the parent. However, unless the computation is

relatively intensive and you are quite certain that the underlying data have not changed, this may not be worth the effort.

Programs on GitHub

  • EmployeesConsole.py
    • Employees.py
      • DoublyLinked.py

15**

The Decorator Pattern

The Decorator pattern provides us with a way to modify the behavior of individual objects without having to create a new derived class. Suppose that we have a program that uses eight objects, but three of them need an additional feature. You could create a derived class for each of these objects, and in many cases, this would be a perfectly acceptable solution. However, if each of these three objects requires different features, this would mean creating three derived classes. Further, if one of the classes has features of both the other classes, you would begin to create complexity that is both confusing and unnecessary.

Decorators can be used on visual objects such as buttons, but Python has a rich suite of nonvisual decorators that we will cover as well.

Now suppose that we wanted to draw a special border around some of the buttons in a toolbar. If we created a new derived button class, this means that all the buttons in this new class would always have this same new border, when this might not be our intent.

Instead, we create a Decorator class that decorates the buttons. Then we derive any number of specific Decorators from the main Decorator class, each of which performs a specific kind of decoration. To decorate a button, the Decorator has to be an object derived from the visual environment, so that it can receive paint method calls and forward calls to other useful graphic methods to the object that it is decorating. This is another case in which object containment is favored over object inheritance. The decorator is a graphical object, but it contains the object it is decorating. It may intercept some graphical method calls, perform some additional computation, and may pass them on to the underlying object it is decorating.

Decorating a Button

Applications running under Windows versions up through Windows 10 have a row of flat, unbordered buttons that highlight themselves with outline borders when you move your mouse over them. Some Windows programmers called this toolbar a CoolBar and call the buttons CoolButtons.

Let’s consider how to create this Decorator. Design Patterns suggests that Decorators should be derived from some general Visual Component class and then every message for the actual button should be forwarded from the decorator.

Python does not make this easy to accomplish, so the Decorator in use here is derived from Button. All it does is intercept mouse movements. Design Patterns suggests that classes such as Decorator should be abstract classes and that you should derive all of your actual working (or concrete) decorators from the abstract class. Again, this is not easy in Python because all the base classes that inherit widget behavior are concrete.

Our decorator simply changes the button style when the mouse enters the button and restores it to flat upon exit.

Click here to view code image

  • Derived Button intercepts mouse entry and changes
  • the decoration of the button from flat to raised class Decorator(Button):

def __init__(self, master, **kwargs):

super().__init__(master, **kwargs)

self.configure(relief=FLAT)

self.bind("", self.on_enter) self.bind("", self.on_leave)

def on_enter(self, evt): self.configure(relief=RAISED)

def on_leave(self, evt): self.configure(relief=FLAT)

Using a Decorator

Now that we’ve written a Decorator class, how do we use it? We simply create an instance of the Decorator as a button it is meant to decorate. We can do all of this right in the constructor. Let’s consider a simple program with two CoolButtons and one ordinary Button. We create the layout as follows:

Click here to view code image

  • creates the user interface

class Builder():

def build(self):

root = Tk()

root.geometry("200x100")

root.title("Tk buttons")

#create two decorated buttons and one normal cbut = CButton(root)

dbut = DButton(root)

qbut = Button(root, text="Quit", command=quit)

cbut.pack( pady=3)

dbut.pack( pady=3)

qbut.pack()

Figure 15-1 shows this program, with the mouse hovering over one of the buttons.

Figure 15-1 Mouse hovering over the C button

And it is then perfectly possible to have the two buttons with different decorators. A similar approach is possible using the tkinter ttk toolkit; an example is provided on our GitHub repository.

Using Nonvisual Decorators

Decorators are not limited to objects that enhance visual classes. You can add or modify the methods of any object in a similar way. In fact, nonvisual objects can be easier to decorate because there may be fewer methods to intercept and forward.

We have already seen the @property decorator and the @staticmethod decorator in previous chapters. At first, these appear to be compiler instructions or some sort of macro, but decorators are actually the names of functions that you call. For example, there is a staticmethod() function in Python, which you wrap around a method. This simple property marker is easier to read and less prone to error.

For a really simple example suggested by the Python 3.9 docs, consider the following simple wrapper and an empty function it wraps:

Click here to view code image

def deco(func):

  • adds a value to a new function property

func.label = "decorated"

return func

  • Complete empty function,
  • that is decorated by the "deco" decorator @deco

def f():

pass print(f.label)

The function f() does nothing, but the deco wrapper adds a property with the value “decorated.” When you run the program, the print function prints out f.label as

Decorated

Decorated Code

Now, let’s consider another function that you might wrap. It prints out a couple of messages but doesn’t do much.

Click here to view code image

  • decorator that wraps a function def mathFunc(func):

def wrapper(x):

print("b4 func") func(x)

print("after func") return wrapper

We’ll also create a simple two-line function to be wrapped. Click here to view code image

  • print out a name or phrase def sayMath(x): print("math")

Now suppose we want to create a new version of mathfunc that wraps sayMath. We can do this directly, like this:

Click here to view code image

  • create wrapped function sayMath = mathFunc(sayMath)

Now, the sayMath function is replaced by the function wrapped by mathFunc. If we call

sayMath(12)

the program will print :

Click here to view code image

call after making decorator b4 func

math

after func

In other words, the word math is wrapped by the wrapper info from mathFunc.

Now let’s rewrite this wrapper code using a decorator: Click here to view code image

  • Decorator wraps sayMath @mathFunc

def sayMath(x):

print("math")

So, you can see that the @mathFunc decorator simply wraps the sayMath function just as if we had written this:

Click here to view code image

sayMath = mathFunc(sayMath)

print("call after making decorator")

And that is really all there is to Python decorators. These are one-line statements that replace a little more complex way of wrapping (or decorating) code. The only reason they are hard to explain is that there are so few simple examples that make them seem useful.

You can put all the decorators you create in a single file and import them as part of your code. But frankly, you probably won’t think of too many times to do so.

The dataclass Decorator

One of the most useful decorators we have come across is the dataclass decorator.

Whenever you create a new class, you have to go through the boilerplate of setting up the __init__ method and copying some values into the instance variables. For example, in this simple Employee class, you generally would write this:

Click here to view code image

class Employee:

def __init__(self, frname:str, lname:str, idnum:int):

self.frname = frname self.lname = lname self.idnum = idnum

where you declare the arguments to the init method and then copy them

into variables for that instance. Well, if this happens pretty much every time you create a class, why not automate it?

This is what the dataclass decorator does for you. If you use this decorator, your code is reduced to

@dataclass

class Employee:

frname: str

lname: str

idnum: int

and the init method and the copying is filled in for you.

You also need to import the library that contains this function, but only once per module:

Click here to view code image

from dataclasses import dataclass

So, when you go to create an instance of Employee, you proceed as usual: Click here to view code image

emp = Employee('Sarah', 'Smythe', 123) print(emp.nameString())

The arguments are in the same order as in the variable list. In fact, IDEs such as PyCharm recognize this decorator (which is just a function call under the covers) and pop up the variable list shown in Figure 15-2.

Figure 15-2 Info the PyCharm IDE displays regarding the Employee constructor

Using dataclass with Default Values

The dataclass decorator handles default values in the same way: class Employee2:

frname: str

lname: str

idnum: int

town: str = "Stamford"

state: str = 'CT'

zip: str = '06820'

The class then works just fine. Doesn’t that make class creation a lot easier?

Decorators, Adapters, and Composites

As noted in Design Patterns, there is an essential similarity among these classes that you may have recognized. Adapters also seem to “decorate” an existing class. However, their function is to change the interface of one or more classes to one that is more convenient for a particular program. Decorators add methods to functions but not to classes. You can also imagine that a composite consisting of a single item is essentially a decorator. Once again, however, the intent is different.

Consequences of the Decorator Pattern

The Decorator pattern provides a more flexible way to add responsibilities to a function in a class than by using inheritance. It also enables you to customize a class without creating subclasses high in the inheritance hierarchy. Design Patterns points out two disadvantages of the Decorator pattern, however. One is that a Decorator and its enclosed component are not identical. Thus, tests for object type will fail. The second is that Decorators can lead to a system with “lots of little objects” that all look alike to the programmer trying to maintain the code. This can be a maintenance headache.

Decorator and Façade evoke similar images in building architecture. In design pattern terminology, however, the Façade is a way of hiding a complex system inside a simpler interface, whereas Decorator adds function by wrapping a class. We’ll take up the Façade pattern in the next chapter.

Programs on GitHub

  • SimpleDecoratorTk.py: Adds value to a new function property using tkinter
  • SimpleDecoratorTtk.py: Adds value to a new function property using the tkinter ttk toolkit
  • DecoCode.py: Decorates a math function
    • Decofunc.py: Interior function used as decorator
      • Dclass.py: Employee class with dataclass
        • Dclasse.py: Employee class without dataclass

16**

The Façade Pattern

As your programs evolve and develop, they frequently grow in complexity. In fact, for all the excitement about using design patterns, these patterns sometimes generate so many classes that it is difficult to understand the program’s flow. Furthermore, there may be a number of complicated subsystems, each of which has its own complex interface.

The Façade pattern enables you to simplify this complexity by providing a simplified interface to these subsystems. This simplification may in some cases reduce the flexibility of the underlying classes, but it usually still provides all the function needed for all but the most sophisticated users. These users can still, of course, access the underlying classes and methods.

Fortunately, we don’t have to write a complex system to provide an example of where a Façade can be useful. Python provides a set of classes that connect to databases using an interface called Open Database Connectivity (ODBC). You can connect to any database for which the manufacturer has provided an ODBC connection class: almost every database on the market.

A database is essentially a set of tables in which columns in one table are related to some data in another table, such as stores, foods, and prices. You create queries that produce results computed from those tables. The queries are written in Structured Query Language (SQL) and the results are usually a new table that contains rows computed from the other tables.

The Python interface is more procedural than object oriented and can be simplified using a few objects based on the four data objects in Figure 16-1.

Figure 16-1 Database objects using Façade

We started down this path using the popular MySQL database, a full- fledged industrial-strength database that you can download and use for free. You can install it and run it on your laptop or on a server where a number of people can access the data. However, for simpler cases, where you don’t need to share the data, a colleague suggested we also should look at the SQLite database. In both cases, these databases run on nearly all computing platforms and Python provides drivers to connect to them. Each SQLite database is a separate file on your computer. It is not embedded in some complex management system, and you can easily send that file to another user when that is useful.

However, by designing a Façade consisting of a Database class and a Results class, we can build a much more usable system for any database you decide to use.

For our examples, we have created a groceries database with just three tables—Foods, Stores, and Prices. Figure 16-2 shows the Foods table. And we were able to create this simple database using the free MySQL Workbench application. There is a similar tool for SQLite called SQLite Studio.

Figure 16-2 MySQL Workbench

Tables may have as many columns as you like, but one column must be the primary key (usually an integer). This table has only the key and food names. The other two tables are the stores and the prices (see Figure 16-3).

Figure 16-3 Stores table and part of the Prices table

Figure 16-3 shows the complete Stores table on the top and a section of the Prices table on the bottom. The Prices table shows a key from the Food table, a key from the Stores table, and a price. So, line 1 shows that Food 1 (Apples) at Store 1 (Stop and Shop) has a price of $0.27 each. (These are real store names, but the prices are entirely fictitious.)

We can then use a SQL query to get all the prices of apples, for example.

Building the Façade Classes

Now let’s consider how to connect to the MySQL database. We first must load the database driver:

import pymysql

Then we use the connect function to connect to a database. Note that these arguments require keyword names.

Click here to view code image

db = pymysql.connect(host=self.host,

user=self.userid, password=self.pwd)

These arguments are the server, the username, the password, and the database name.

If we want to list the names of the tables in the database, we need to query the database for the names:

Click here to view code image

db.cursor.execute("show tables") rows = cursor.fetchall()

for r in rows:

print(r)

This gives you the following, which are essentially single element tuples:

('foods',) ('prices',) ('stores',)

If you want to execute a query, to get the prices of apples, for example, you do it like this:

Click here to view code image

  • execute SQL query using execute() method.

cursor.execute( """select foods.foodname, stores.storename, prices.price from prices

join foods on (foods.foodkey=prices.foodkey)

join stores on (stores.storekey = prices.storekey )

where foods.foodname='Apples' order by price""" row = cursor.fetchone()

while row is not None:

print(row)

row = cursor.fetchone()

The result is three tuples: Click here to view code image

('Apples', 'Stop and Shop', 0.27) ('Apples', 'Village Market', 0.29) ('Apples', 'ShopRite', 0.33)

This is a little clunky to manage and is entirely procedural, with no classes.

One simplifying assumption we can make is that the exceptions that all these database class methods throw do not need complex handling. For the most part, the methods will work without error unless the network connection to the database fails. Thus, we can wrap all of these methods in classes in which we simply print the infrequent errors and take no further action.

This makes it possible to make four enclosing classes, as shown in Figure 16-1: the Database class, the Table class, the Query class, and the Results class. These constitute the Façade pattern we have been leading up to.

The Database class here not only connects to the server and opens a database, but it also creates an array of Table objects.

Click here to view code image

class MysqlDatabase(Database):

def __init__(self, host, username, password,dbname): self._db = pymysql.connect(host=host, user=username,

password=password, database=dbname)

self._dbname = dbname

self._cursor = self._db.cursor()

@property

def cursor(self):

return self._cursor

def getTables(self):

self._cursor.execute("show tables")

  • create array of table objects self.tables = []

rows = self._cursor.fetchall()

for r in rows:

self.tables.append( Table(self._cursor, r))

return self.tables

The Table object gets the column names and stores them: Click here to view code image

class Table():

def __init__(self, cursor, name):

self.cursor = cursor

self.tname = name[0] # first of tuple

  • get column names self.cursor.execute("show columns from " + self.tname) self.columns = self.cursor.fetchall()

@property

def name(self): # gets table name

return self.tname

  • returns a list of columns def getColumns(self): return self.columns

The Query class executes the query and returns the results: Click here to view code image

class Query():

def __init__(self, cursor, qstring):

self.qstringMaster = qstring #master copy self.qstring = self.qstringMaster self.cursor = cursor

  • executes the query and returns all results

def execute(self):

print (self.qstring) self.cursor.execute(self.qstring)

rows = self.cursor.fetchall()

return Results(rows)

We store the query string in qstringMaster so that it can be copied and modified if you want to use the same query for different foods.

Finally, the simple Results class just keeps the rows. Click here to view code image

class Results():

def __init__(self, rows):

self.rows = rows

def getRows(self):

return self.rows

You could enhance the class by adding an iterator to get the rows one by one and then formatting them if you wanted.

These simple classes allow us to write a program for opening a database; displaying its table names, column names, and contents; and running a simple SQL query on the database.

The DBObjects program accesses a simple database that contains food prices at three local markets (see Figure 16-4).

Figure 16-4 Grocery store pricing with DBObjects

Clicking a table name shows you the column names; clicking a column name shows you the contents of that column. If you click the Get Prices button, you display the food prices sorted by store for any food you pick from the list box on the right.

This program starts by connecting to the database and getting a list of the table names:

Click here to view code image

db = MysqlDatabase('localhost', 'newuser',

'new_user','groceries')

Then the program runs a simple query for table names. Each table runs a query for column names once when created. The list of column contents is generated by a query when you click the column name in the middle list box.

Creating Databases and Tables

With slight modifications to the Database, Table, and Query classes, you can create a database and create and populate tables. These classes then generate the needed SQL. You can find the complete code in our GitHub repository.

Here is how we create the database and tables for our groceries example. Click here to view code image

db = Database("localhost", "newuser", "new_user") db.create("groceries")

med = Mediator(db) #keeps the primary key string

  • Create food table

foodtable = Table(db, "foods", med)

  • primary key foodtable.addColumn(Intcol("foodkey", True, med)) foodtable.addColumn (Charcol("foodname", 45)) foodtable.create()

vals = [(1, 'Apples'), (2, 'Oranges'), (3, 'Hamburger'), (4, 'Butter'), (5, 'Milk'), (6, 'Cola'), (7, 'Green beans')

]

foodtable.addRows(vals)

  • create store table storetable = Table(db, "stores", med)

storetable.addColumn( Intcol("storekey", True, med)) # primary key storetable.addColumn(Charcol("storename", 45))

storetable.create()

vals = [(1, 'Stop and Shop'), (2, 'Village Market'), (3, 'Shoprite')] storetable.addRows(vals)

Although the data for the Prices table is longer, the approach is exactly the same:

Click here to view code image

pricetable = Table(db, pricetable.addColumn(Intcol( pricetable.addColumn(Intcol( pricetable.addColumn(Intcol( pricetable.addColumn(Floatcol( pricetable.create()

"prices", med)

"pricekey", True, med)) # primary key "foodkey", False, med))

"storekey", False, med))

"price"))

vals = [( 1, 1, 1, 0.27),

(2, 2, 1, 0.36), (3, 3, 1, 1.98), (4, 4, 1, 2.39), (5, 5, 1, 1.98),

  • and so forth

]

pricetable.addRows(vals)

Using the SQLite Version

There are only very small differences in the Database and Table code for Sqlite. And to illustrate the great power of classes, we can create a derived class from Database with some slight changes to the methods. For example, connecting to a SQLite database just means specifying a filename. And SQLite does not have a “show tables” SQL command, but you can still get the table names from a master table within the database file:

Click here to view code image

class SqltDatabase(Database):

def __init__(self, *args):

self._db = sqlite3.connect(args[0]) self._dbname = args[0] self._cursor = self._db.cursor()

def commit(self):

self._db.commit()

def create(self, dbname):

pass

def getTables(self): tbQuery = Query(self.cursor,

"""select name from

sqlite_master where type='table'""")

  • create array of table objects

rows = tbQuery.execute().getRows()

for r in rows:

self.tables.append( SqltTable(self._db, r))

return self.tables

Changes to the derived SqltTable class are likewise pretty simple, and the Groceries app using SQLite runs and looks exactly the same as the one for the MySQL version.

Consequences of the Façade

The Façade pattern shields clients from complex subsystem components and provides a simpler programming interface for the general user. However, it does not prevent advanced users from going to deeper, more complex classes when necessary.

In addition, the Façade pattern allows you to make changes in the underlying subsystems without requiring changes in the client code, and it reduces compilation dependencies.

Programs on GitHub

  • Dbtest.py: test query without Façade
    • SimpledbObjects.py: Queries without UI
      • DBObjects.py: Complete set of DB classes
        • MysqlDatabase.py: Connects to MySQL
  • SqltDatabase.py: Connects to SqLite
    • Makedatabase.py: Creates the groceries MySQL database
      • Makesqlite.py: Creates a SQLite database
        • Grocerydisplay.py: Displays groceries using MySQL
  • GroceryDispLite: Displays groceries using SQLite

Notes on MySQL

MySQL was always an open source project, but it has a complicated history: It was sold to Sun Microsystems, which was then taken over by Oracle, who now supports MySQL for free, although it offers a paid version as well. The original MySQL developers left the project at that time, taking the MySQL code with them and forming MariaDB (which is also freely available).

You can download and install MySQL directly from the Oracle website (mysql.com) for most platforms. For Windows, use the .msi installer, which is supposed to install everything you need for Python to work with MySQL.

You will also need to install the pymysql library using pip:

pip install pymysql

For PyCharm, this can be done directly from the command line. For VSCODE, you need to open a command line within VSCODE to install this library in the right place.

When you install MySQL, you also need to create a user other than root. Make sure that Authentication Type is set to standard and that Administrative Roles is set to DBA.

Using SQLite

You can download the Windows ZIP file (as well as many others) from sqlite.com. Unzip it into any convenient directory and add that directory to your path. Sqlite Studio is available at sqlitestudio.pl.

References

https://dev.mysql.com/doc/refman/8.0/en/windows-installation.html

17**

The Flyweight Pattern

Sometimes in programming, it seems that you need to generate a very large number of small class instances to represent data. You can greatly reduce the number of different classes that you need to instantiate if you can recognize that the instances are fundamentally the same except for a few parameters. If you can move those variables outside the class instance and pass them in as part of a method call, the number of separate instances can be greatly reduced by sharing them.

The Flyweight design pattern provides an approach for handling such classes. It refers to the instance’s intrinsic data, which makes the instance unique, and the extrinsic data, which is passed in as arguments. The Flyweight pattern is appropriate for small, fine-grained classes such as individual characters or icons on the screen. For example, you might be drawing a series of icons on the screen in a window, where each represents a person or data file as a folder (see Figure 17-1).

In this case, it does not make sense to have an individual class instance for each folder that remembers the person’s name and the icon’s screen position. Typically, these icons are one of a few similar images, and the position where they are drawn is calculated dynamically based on the window’s size.

In another example in Design Patterns, each character in a document is represented as a single instance of a character class, but the positions where the characters are drawn on the screen are kept as external data so that there needs to be only one instance of each character, rather than one for each appearance of that character.

What Are Flyweights?

Flyweights are sharable instances of a class. At first, it might seem that each class is a Singleton, but in fact there might be a small number of instances, such as one for every character or one for every icon type. The number of instances that are allocated must be decided as the class instances are needed. This is usually accomplished with a FlyweightFactory class. This factory class usually is a Singleton because it needs to keep track of whether a particular instance has been generated. The class then returns either a new instance or a reference to one it has already generated.

To decide whether some part of your program is a candidate for using Flyweights, consider whether it is possible to remove some data from the class and make it extrinsic. If this makes it possible to reduce greatly the number of different class instances your program needs to maintain, this might be a case where Flyweights will help.

Example Code

Suppose we want to draw a small folder icon with a name under it for each person in an organization. If this is a large organization, there could be a large number of such icons, but the icons are actually all the same graphical image with different text labels. Even if we have two icons, one for “is selected” and one for “not selected,” the number of different icons is small. In such a system, having an icon object for each person, with its own coordinates, name, and selected state, is a waste of resources. Figure 17-1 shows two such icons.

Figure 17-1 Folders as Flyweights

Instead, we’ll create a FolderFactory that returns either the selected or the unselected folder drawing class but does not create additional instances after one of each has been created. Because this is such a simple case, we just create them both at the outset and then return one or the other:

Click here to view code image

  • returns a selected or unselected folder class FolderFactory():

def __init__(self, canvas): brown = "#5f5f1c"

self.selected = Folder(brown, canvas)

self.unselected = Folder("yellow", canvas)

def getFolder(self, isSelected):

if isSelected:

return self.selected else:

return self.unselected

For cases where more instances could exist, the factory could keep a table of the ones it has already created and then create new ones only if they aren’t already in the table.

The unique thing about using Flyweights, however, is that we pass the coordinates and the name to be drawn into the folder when we draw it. These coordinates are the extrinsic data that allow us to share the folder objects and, in this case, create only two instances. The complete folder class shown next simply creates a folder instance with one background color or the other and has a public Draw method that draws the folder at the point you specify.

Click here to view code image

  • draws a folder class Folder(): W =50

H=30

def __init__(self, color, canvas:Canvas):

self._color = color

self.canvas = canvas

  • draw the folder

def draw (self, tx, ty, name): self.canvas.create_rectangle(tx, ty,

tx+Folder.W, ty+Folder.H, fill="black")

self.canvas.create_text(tx+20, ty+Folder.H+15, text=name) self.canvas.create_rectangle(

tx+1, ty+1, tx + Folder.W-1, ty + Folder.H-1,

fill=self._color)

#----And so forth ----

To use a Flyweight class like this, your main program must calculate the position of each folder as part of its paint routine and then pass the coordinates to the folder instance. This is actually rather common because

you need a different layout depending on the window’s dimensions, and you do not want to have to keep telling each instance where its new location is going to be. Instead, we compute it dynamically during the paint routine.

Note that we could have generated an array of folders at the outset and simply scanned through the array to draw each folder. Such an array is not as wasteful as a series of different instances because it is actually an array of references to one of only two folder instances. However, because we want to display one folder as selected and we want to be able to change which folder is selected dynamically, we just use the FolderFactory itself to give us the correct instance each time:

Click here to view code image

def repaint(self): j = 0

row = BuildUI.TOP x= BuildUI.LEFT

  • look for whether any is selected and
  • use the factory to create it

for nm in self.namelist:

f = self.factory.getFolder( nm == self.selectedName)

f.draw( x, row, nm)

x += BuildUI.HSPACE

j += 1

if j > BuildUI.ROWMAX:

j = 0

row += BuildUI.VSPACE

x = BuildUI.LEFT

The FlyCanvas class is the main UI class where the folders are arranged and drawn. It contains one instance of the FolderFactory and one instance of

the Folder class. The FolderFactory class contains two instances of Folder: selected and unselected. The FolderFactory returns one or the other of these to the FlyCanvas.

Selecting a Folder

Since we have two folder instances that we termed selected and unselected, we’d like to be able to select folders by moving the mouse over them. In the paint routine shown earlier, we simply remember the name of the folder that was selected and ask the factory to return a selected folder for it. The folders are not individual instances, so we can’t listen for mouse motion within each folder instance. In fact, even if we did listen within a folder, we’d need to have a way to tell the other instances to deselect themselves.

Instead, we check for a mouse click at the canvas level. If the mouse is found to be within a folder rectangle, we make that corresponding name the selected name. This allows us to just check each name when we redraw and create a selected folder instance where it is needed:

Click here to view code image

  • search to see if click is inside a folder
  • changes the selected name so repaint draws
  • a new selected folder

def mouseClick(self, evt):

self.selectedName= ""

found = False

j = 0

row =FlyCanvas.TOP

x = FlyCanvas.LEFT

self.selectedName = "" #blank if not in folder for nm in self.namelist:

if x < evt.x and evt.x < (x+ Folder.W):

if row < evt.y and \

evt.y < (row+Folder.H):

self.selectedName = nm found = True

j += 1

x += FlyCanvas.HSPACE

if j > FlyCanvas.ROWMAX:

j=0

row += FlyCanvas.VSPACE

x = FlyCanvas.LEFT

self.repaint()

Copy-on-Write Objects

The Flyweight pattern uses just a few object instances to represent many different objects in a program. All of them normally have the same base properties as intrinsic data and a few properties representing extrinsic data that vary with each manifestation of the class instance. However, some of

these instances might eventually take on new intrinsic properties (such as shape or folder tab position) and require a new specific instance of the class to represent them. Instead of creating these in advance as special subclasses, you can copy the class instance and change its intrinsic properties when the program flow indicates that a new separate instance is required. The class thus copies itself when the change becomes inevitable, changing those intrinsic properties in the new class. We call this process copy-on-write and can build this process into Flyweights as well as a number of other classes, such as the Proxy pattern discussed in the next chapter.

Program on GitHub

  • FlyFolders.py

'James Cooper - Python Programming with D' 카테고리의 다른 글

The Bridge Pattern  (0) 2023.03.23
Summary of Creational Patterns  (0) 2023.03.23
The Prototype Pattern  (0) 2023.03.23
The Prototype Pattern  (0) 2023.03.23
The Singleton Pattern  (0) 2023.03.23

댓글