diff --git a/.gitignore b/.gitignore index ba74660..3d1f124 100644 --- a/.gitignore +++ b/.gitignore @@ -1,3 +1,5 @@ +*.sublime-* + # Byte-compiled / optimized / DLL files __pycache__/ *.py[cod] diff --git a/Martelli/borg.py b/Martelli/borg.py new file mode 100644 index 0000000..9315fc1 --- /dev/null +++ b/Martelli/borg.py @@ -0,0 +1,24 @@ +""" + >>> b1 = Borg() + >>> b2 = Borg() + >>> b1 is b2 + False + >>> b1.i_know_kung_fu = True + >>> b2.i_know_kung_fu + True + >>> del b2.i_know_kung_fu + >>> b1.i_know_kung_fu + Traceback (most recent call last): + ... + AttributeError: 'Borg' object has no attribute 'i_know_kung_fu' +""" + + +class Borg: + + _estado_compartilhado = {} + + def __new__(cls, *args, **kwargs): + obj = super().__new__(cls, *args, **kwargs) + obj.__dict__ = cls._estado_compartilhado + return obj diff --git a/Martelli/embrulho.py b/Martelli/embrulho.py new file mode 100644 index 0000000..6ff1010 --- /dev/null +++ b/Martelli/embrulho.py @@ -0,0 +1,45 @@ +""" +A classe EmbrulhoRestritor demonstra o uso de composição para limitar +o acesso a atributos de instâncias de outra classe qualquer. + + >>> rex = Cao() + >>> rex.latir() + Au! + >>> rex.morder() + Nhac! + >>> rex2 = EmbrulhoRestritor(rex, ['morder']) + >>> rex2.latir() + Au! + >>> rex2.morder() + Traceback (most recent call last): + ... + AttributeError: atributo bloqueado 'morder' + +""" + + +class EmbrulhoRestritor: + + def __init__(self, embrulhado, bloqueios): + self._embrulhado = embrulhado + self._bloqueios = bloqueios + + def __getattr__(self, nome): + if nome in self._bloqueios: + msg = 'atributo bloqueado {!r}'.format(nome) + raise AttributeError(msg) + else: + return getattr(self._embrulhado, nome) + + def exportar(self): + return {'embrulhado': self._embrulhado, + 'bloqueios': self._bloqueios} + + +class Cao: + + def latir(self): + print('Au!') + + def morder(self): + print('Nhac!') diff --git a/Martelli/singleton.py b/Martelli/singleton.py new file mode 100644 index 0000000..85cfe40 --- /dev/null +++ b/Martelli/singleton.py @@ -0,0 +1,24 @@ +""" + >>> t1 = Treco() + >>> t2 = Treco() + >>> t1 is t2 + False + >>> s1 = Singleton() + >>> s2 = Singleton() + >>> s1 is s2 + True + >>> id(s1), id(s2) + +""" + + +class Singleton: + + def __new__(cls, *args, **kwargs): + if not hasattr(cls, '_a_instancia'): + cls._a_instancia = super().__new__(cls, *args, **kwargs) + return cls._a_instancia + + +class Treco: + pass diff --git a/O_que_sao_design_patterns.rst b/O_que_sao_design_patterns.rst index 6b65564..e10d64d 100644 --- a/O_que_sao_design_patterns.rst +++ b/O_que_sao_design_patterns.rst @@ -2,6 +2,8 @@ O que são Design Patterns ============================ +**ATUALIZAÇÃO**: Fiz um `vídeo de 8 minutos `_ onde apresento conceitos de **design patterns** usando blocos de LEGO® e outros exemplos. + .. image:: patterns-gof-book.png :alt: Cover of the original Design Patterns book :align: right @@ -21,5 +23,9 @@ Aprender padrões de projeto é uma forma de aperfeiçoar suas habilidades para ---- -Para aprender padrões de projeto na prática, usando a linguagem Python, faça o curso **Python Patterns** com Luciano Ramalho. A primeira turma presencial será em Porto Alegre na semana do FISL 16 (`inscrições `_). +**Nota**: O design pattern **Iterator** já vem embutido na linguagem Python, portanto programadores Python não precisam implementá-lo. Mesmo assim, o entendimento do design pattern original ajuda a entender o potencial e os casos de uso dos iteradores embutidos na linguagem. Outros design patterns são simplificados em Python, se comparados a implementações clássicas em C++ ou Java. + +---- + +Para aprender padrões de projeto na prática, usando a linguagem Python, faça o curso **Python Patterns** com Luciano Ramalho. A primeira turma **presencial** será São Paulo no fim de semana de 15 e 16 de agosto de 2015 (`inscrições `_). A primeira turma **online** começa em 29 de julho de 2015, às 20h (`instruções `_). diff --git a/README.md b/README.md index df06dea..fc91635 100644 --- a/README.md +++ b/README.md @@ -1,3 +1,3 @@ # pythonpatterns -Futuras instações dos exemplos do curso Python Patterns com Luciano Ramalho +Exemplos do curso Python Patterns com Luciano Ramalho diff --git a/adapter/teste.txt b/adapter/teste.txt new file mode 100644 index 0000000..2644b32 --- /dev/null +++ b/adapter/teste.txt @@ -0,0 +1 @@ +TESTANDO... \ No newline at end of file diff --git a/adapter/upperfile.py b/adapter/upperfile.py new file mode 100644 index 0000000..8597053 --- /dev/null +++ b/adapter/upperfile.py @@ -0,0 +1,30 @@ +""" + >>> with UppercasingFile('teste.txt', 'wt', encoding='utf-8') as f: + ... f.write('Testando...') + 11 + >>> with UppercasingFile('teste.txt', encoding='utf-8') as f: + ... s = f.read() + >>> s + 'TESTANDO...' + +""" + + + +class UppercasingFile: + def __init__(self, *a, **k): + self.f = open(*a, **k) + + def write(self, data): + return self.f.write(data.upper()) + + def __getattr__(self, name): + return getattr(self.f, name) + + def __enter__(self, *a, **k): + return self + + def __exit__(self, *a, **k): + self.f.__exit__(*a, **k) + + diff --git a/command/turtle.py b/command/turtle.py new file mode 100644 index 0000000..00dcdf3 --- /dev/null +++ b/command/turtle.py @@ -0,0 +1,73 @@ +import cmd, sys +from turtle import * + +class TurtleShell(cmd.Cmd): + intro = 'Welcome to the turtle shell. Type help or ? to list commands.\n' + prompt = '(turtle) ' + file = None + + # ----- basic turtle commands ----- + def do_forward(self, arg): + 'Move the turtle forward by the specified distance: FORWARD 10' + forward(*parse(arg)) + def do_right(self, arg): + 'Turn turtle right by given number of degrees: RIGHT 20' + right(*parse(arg)) + def do_left(self, arg): + 'Turn turtle left by given number of degrees: LEFT 90' + left(*parse(arg)) + def do_goto(self, arg): + 'Move turtle to an absolute position with changing orientation. GOTO 100 200' + goto(*parse(arg)) + def do_home(self, arg): + 'Return turtle to the home position: HOME' + home() + def do_circle(self, arg): + 'Draw circle with given radius an options extent and steps: CIRCLE 50' + circle(*parse(arg)) + def do_position(self, arg): + 'Print the current turle position: POSITION' + print('Current position is %d %d\n' % position()) + def do_heading(self, arg): + 'Print the current turle heading in degrees: HEADING' + print('Current heading is %d\n' % (heading(),)) + def do_color(self, arg): + 'Set the color: COLOR BLUE' + color(arg.lower()) + def do_undo(self, arg): + 'Undo (repeatedly) the last turtle action(s): UNDO' + def do_reset(self, arg): + 'Clear the screen and return turtle to center: RESET' + reset() + def do_bye(self, arg): + 'Stop recording, close the turtle window, and exit: BYE' + print('Thank you for using Turtle') + self.close() + bye() + return True + + # ----- record and playback ----- + def do_record(self, arg): + 'Save future commands to filename: RECORD rose.cmd' + self.file = open(arg, 'w') + def do_playback(self, arg): + 'Playback commands from a file: PLAYBACK rose.cmd' + self.close() + with open(arg) as f: + self.cmdqueue.extend(f.read().splitlines()) + def precmd(self, line): + line = line.lower() + if self.file and 'playback' not in line: + print(line, file=self.file) + return line + def close(self): + if self.file: + self.file.close() + self.file = None + +def parse(arg): + 'Convert a series of zero or more numbers to an argument tuple' + return tuple(map(int, arg.split())) + +if __name__ == '__main__': + TurtleShell().cmdloop() diff --git a/composite/demo.py b/composite/demo.py new file mode 100644 index 0000000..5f45488 --- /dev/null +++ b/composite/demo.py @@ -0,0 +1,132 @@ +from menu import Menu, MenuItem + +pancake_house_menu = Menu("PANCAKE HOUSE MENU", "Breakfast") +diner_menu = Menu("DINER MENU", "Lunch") +cafe_menu = Menu("CAFE MENU", "Dinner") +dessert_menu = Menu("DESSERT MENU", "Dessert of course!") +coffee_menu = Menu("COFFEE MENU", "Stuff to go with your afternoon coffee") + +all_menus = Menu("ALL MENUS", "All menus combined") + +all_menus.append(pancake_house_menu) +all_menus.append(diner_menu) +all_menus.append(cafe_menu) + +pancake_house_menu.append(MenuItem( + "K&B's Pancake Breakfast", + "Pancakes with scrambled eggs, and toast", + True, + 2.99)) +pancake_house_menu.append(MenuItem( + "Regular Pancake Breakfast", + "Pancakes with fried eggs, sausage", + False, + 2.99)) +pancake_house_menu.append(MenuItem( + "Blueberry Pancakes", + "Pancakes made with fresh blueberries, and blueberry syrup", + True, + 3.49)) +pancake_house_menu.append(MenuItem( + "Waffles", + "Waffles, with your choice of blueberries or strawberries", + True, + 3.59)) + +diner_menu.append(MenuItem( + "Vegetarian BLT", + "(Fakin') Bacon with lettuce & tomato on whole wheat", + True, + 2.99)) +diner_menu.append(MenuItem( + "BLT", + "Bacon with lettuce & tomato on whole wheat", + False, + 2.99)) +diner_menu.append(MenuItem( + "Soup of the day", + "A bowl of the soup of the day, with a side of potato salad", + False, + 3.29)) +diner_menu.append(MenuItem( + "Hotdog", + "A hot dog, with saurkraut, relish, onions, topped with cheese", + False, + 3.05)) +diner_menu.append(MenuItem( + "Steamed Veggies and Brown Rice", + "Steamed vegetables over brown rice", + True, + 3.99)) + +diner_menu.append(MenuItem( + "Pasta", + "Spaghetti with Marinara Sauce, and a slice of sourdough bread", + True, + 3.89)) + +diner_menu.append(dessert_menu) + +dessert_menu.append(MenuItem( + "Apple Pie", + "Apple pie with a flakey crust, topped with vanilla icecream", + True, + 1.59)) + +dessert_menu.append(MenuItem( + "Cheesecake", + "Creamy New York cheesecake, with a chocolate graham crust", + True, + 1.99)) +dessert_menu.append(MenuItem( + "Sorbet", + "A scoop of raspberry and a scoop of lime", + True, + 1.89)) + +cafe_menu.append(MenuItem( + "Veggie Burger and Air Fries", + "Veggie burger on a whole wheat bun, lettuce, tomato, and fries", + True, + 3.99)) +cafe_menu.append(MenuItem( + "Soup of the day", + "A cup of the soup of the day, with a side salad", + False, + 3.69)) +cafe_menu.append(MenuItem( + "Burrito", + "A large burrito, with whole pinto beans, salsa, guacamole", + True, + 4.29)) + +cafe_menu.append(coffee_menu) + +coffee_menu.append(MenuItem( + "Coffee Cake", + "Crumbly cake topped with cinnamon and walnuts", + True, + 1.59)) +coffee_menu.append(MenuItem( + "Bagel", + "Flavors include sesame, poppyseed, cinnamon raisin, pumpkin", + False, + 0.69)) +coffee_menu.append(MenuItem( + "Biscotti", + "Three almond or hazelnut biscotti cookies", + True, + 0.89)) + + +class Garconete: + + def __init__(self, menu): + self.menu = menu + + def display_menu(self): + self.menu.display() + +jane = Garconete(all_menus) + +jane.display_menu() diff --git a/composite/java/README.md b/composite/java/README.md new file mode 100644 index 0000000..45e96fc --- /dev/null +++ b/composite/java/README.md @@ -0,0 +1,4 @@ +# Head First Design Patterns + +Java code example from chapter 9 of the book [Head First Design Patterns](http://shop.oreilly.com/product/9780596007126.do) (O'Reilly, 2004) by **Eric Freeman**, **Elisabeth Robson**, **Bert Bates** and **Kathy Sierra**. + diff --git a/composite/java/headfirst/composite/menu/Menu.java b/composite/java/headfirst/composite/menu/Menu.java new file mode 100755 index 0000000..d20283c --- /dev/null +++ b/composite/java/headfirst/composite/menu/Menu.java @@ -0,0 +1,48 @@ +package headfirst.composite.menu; + +import java.util.Iterator; +import java.util.ArrayList; + +public class Menu extends MenuComponent { + ArrayList menuComponents = new ArrayList(); + String name; + String description; + + public Menu(String name, String description) { + this.name = name; + this.description = description; + } + + public void add(MenuComponent menuComponent) { + menuComponents.add(menuComponent); + } + + public void remove(MenuComponent menuComponent) { + menuComponents.remove(menuComponent); + } + + public MenuComponent getChild(int i) { + return (MenuComponent)menuComponents.get(i); + } + + public String getName() { + return name; + } + + public String getDescription() { + return description; + } + + public void print() { + System.out.print("\n" + getName()); + System.out.println(", " + getDescription()); + System.out.println("---------------------"); + + Iterator iterator = menuComponents.iterator(); + while (iterator.hasNext()) { + MenuComponent menuComponent = + (MenuComponent)iterator.next(); + menuComponent.print(); + } + } +} diff --git a/composite/java/headfirst/composite/menu/MenuComponent.java b/composite/java/headfirst/composite/menu/MenuComponent.java new file mode 100755 index 0000000..03477f1 --- /dev/null +++ b/composite/java/headfirst/composite/menu/MenuComponent.java @@ -0,0 +1,33 @@ +package headfirst.composite.menu; + +import java.util.*; + +public abstract class MenuComponent { + + public void add(MenuComponent menuComponent) { + throw new UnsupportedOperationException(); + } + public void remove(MenuComponent menuComponent) { + throw new UnsupportedOperationException(); + } + public MenuComponent getChild(int i) { + throw new UnsupportedOperationException(); + } + + public String getName() { + throw new UnsupportedOperationException(); + } + public String getDescription() { + throw new UnsupportedOperationException(); + } + public double getPrice() { + throw new UnsupportedOperationException(); + } + public boolean isVegetarian() { + throw new UnsupportedOperationException(); + } + + public void print() { + throw new UnsupportedOperationException(); + } +} diff --git a/composite/java/headfirst/composite/menu/MenuItem.java b/composite/java/headfirst/composite/menu/MenuItem.java new file mode 100755 index 0000000..cbb0cfd --- /dev/null +++ b/composite/java/headfirst/composite/menu/MenuItem.java @@ -0,0 +1,47 @@ +package headfirst.composite.menu; + +import java.util.Iterator; +import java.util.ArrayList; + +public class MenuItem extends MenuComponent { + String name; + String description; + boolean vegetarian; + double price; + + public MenuItem(String name, + String description, + boolean vegetarian, + double price) + { + this.name = name; + this.description = description; + this.vegetarian = vegetarian; + this.price = price; + } + + public String getName() { + return name; + } + + public String getDescription() { + return description; + } + + public double getPrice() { + return price; + } + + public boolean isVegetarian() { + return vegetarian; + } + + public void print() { + System.out.print(" " + getName()); + if (isVegetarian()) { + System.out.print("(v)"); + } + System.out.println(", " + getPrice()); + System.out.println(" -- " + getDescription()); + } +} diff --git a/composite/java/headfirst/composite/menu/MenuTestDrive.java b/composite/java/headfirst/composite/menu/MenuTestDrive.java new file mode 100755 index 0000000..21876d8 --- /dev/null +++ b/composite/java/headfirst/composite/menu/MenuTestDrive.java @@ -0,0 +1,133 @@ +package headfirst.composite.menu; + +import java.util.*; + +public class MenuTestDrive { + public static void main(String args[]) { + MenuComponent pancakeHouseMenu = + new Menu("PANCAKE HOUSE MENU", "Breakfast"); + MenuComponent dinerMenu = + new Menu("DINER MENU", "Lunch"); + MenuComponent cafeMenu = + new Menu("CAFE MENU", "Dinner"); + MenuComponent dessertMenu = + new Menu("DESSERT MENU", "Dessert of course!"); + MenuComponent coffeeMenu = new Menu("COFFEE MENU", "Stuff to go with your afternoon coffee"); + + MenuComponent allMenus = new Menu("ALL MENUS", "All menus combined"); + + allMenus.add(pancakeHouseMenu); + allMenus.add(dinerMenu); + allMenus.add(cafeMenu); + + pancakeHouseMenu.add(new MenuItem( + "K&B's Pancake Breakfast", + "Pancakes with scrambled eggs, and toast", + true, + 2.99)); + pancakeHouseMenu.add(new MenuItem( + "Regular Pancake Breakfast", + "Pancakes with fried eggs, sausage", + false, + 2.99)); + pancakeHouseMenu.add(new MenuItem( + "Blueberry Pancakes", + "Pancakes made with fresh blueberries, and blueberry syrup", + true, + 3.49)); + pancakeHouseMenu.add(new MenuItem( + "Waffles", + "Waffles, with your choice of blueberries or strawberries", + true, + 3.59)); + + dinerMenu.add(new MenuItem( + "Vegetarian BLT", + "(Fakin') Bacon with lettuce & tomato on whole wheat", + true, + 2.99)); + dinerMenu.add(new MenuItem( + "BLT", + "Bacon with lettuce & tomato on whole wheat", + false, + 2.99)); + dinerMenu.add(new MenuItem( + "Soup of the day", + "A bowl of the soup of the day, with a side of potato salad", + false, + 3.29)); + dinerMenu.add(new MenuItem( + "Hotdog", + "A hot dog, with saurkraut, relish, onions, topped with cheese", + false, + 3.05)); + dinerMenu.add(new MenuItem( + "Steamed Veggies and Brown Rice", + "Steamed vegetables over brown rice", + true, + 3.99)); + + dinerMenu.add(new MenuItem( + "Pasta", + "Spaghetti with Marinara Sauce, and a slice of sourdough bread", + true, + 3.89)); + + dinerMenu.add(dessertMenu); + + dessertMenu.add(new MenuItem( + "Apple Pie", + "Apple pie with a flakey crust, topped with vanilla icecream", + true, + 1.59)); + + dessertMenu.add(new MenuItem( + "Cheesecake", + "Creamy New York cheesecake, with a chocolate graham crust", + true, + 1.99)); + dessertMenu.add(new MenuItem( + "Sorbet", + "A scoop of raspberry and a scoop of lime", + true, + 1.89)); + + cafeMenu.add(new MenuItem( + "Veggie Burger and Air Fries", + "Veggie burger on a whole wheat bun, lettuce, tomato, and fries", + true, + 3.99)); + cafeMenu.add(new MenuItem( + "Soup of the day", + "A cup of the soup of the day, with a side salad", + false, + 3.69)); + cafeMenu.add(new MenuItem( + "Burrito", + "A large burrito, with whole pinto beans, salsa, guacamole", + true, + 4.29)); + + cafeMenu.add(coffeeMenu); + + coffeeMenu.add(new MenuItem( + "Coffee Cake", + "Crumbly cake topped with cinnamon and walnuts", + true, + 1.59)); + coffeeMenu.add(new MenuItem( + "Bagel", + "Flavors include sesame, poppyseed, cinnamon raisin, pumpkin", + false, + 0.69)); + coffeeMenu.add(new MenuItem( + "Biscotti", + "Three almond or hazelnut biscotti cookies", + true, + 0.89)); + + Waitress waitress = new Waitress(allMenus); + + waitress.printMenu(); + } +} diff --git a/composite/java/headfirst/composite/menu/Waitress.java b/composite/java/headfirst/composite/menu/Waitress.java new file mode 100755 index 0000000..ba2df13 --- /dev/null +++ b/composite/java/headfirst/composite/menu/Waitress.java @@ -0,0 +1,15 @@ +package headfirst.composite.menu; + +import java.util.Iterator; + +public class Waitress { + MenuComponent allMenus; + + public Waitress(MenuComponent allMenus) { + this.allMenus = allMenus; + } + + public void printMenu() { + allMenus.print(); + } +} diff --git a/composite/java/headfirst/composite/menuiterator/CompositeIterator.java b/composite/java/headfirst/composite/menuiterator/CompositeIterator.java new file mode 100755 index 0000000..36f14cb --- /dev/null +++ b/composite/java/headfirst/composite/menuiterator/CompositeIterator.java @@ -0,0 +1,45 @@ +package headfirst.composite.menuiterator; + + +import java.util.*; + +public class CompositeIterator implements Iterator { + Stack stack = new Stack(); + + public CompositeIterator(Iterator iterator) { + stack.push(iterator); + } + + public Object next() { + if (hasNext()) { + Iterator iterator = (Iterator) stack.peek(); + MenuComponent component = (MenuComponent) iterator.next(); + if (component instanceof Menu) { + stack.push(component.createIterator()); + } + return component; + } else { + return null; + } + } + + public boolean hasNext() { + if (stack.empty()) { + return false; + } else { + Iterator iterator = (Iterator) stack.peek(); + if (!iterator.hasNext()) { + stack.pop(); + return hasNext(); + } else { + return true; + } + } + } + + public void remove() { + throw new UnsupportedOperationException(); + } +} + + diff --git a/composite/java/headfirst/composite/menuiterator/Menu.java b/composite/java/headfirst/composite/menuiterator/Menu.java new file mode 100755 index 0000000..8256287 --- /dev/null +++ b/composite/java/headfirst/composite/menuiterator/Menu.java @@ -0,0 +1,55 @@ +package headfirst.composite.menuiterator; + +import java.util.Iterator; +import java.util.ArrayList; + +public class Menu extends MenuComponent { + + ArrayList menuComponents = new ArrayList(); + String name; + String description; + + public Menu(String name, String description) { + this.name = name; + this.description = description; + } + + public void add(MenuComponent menuComponent) { + menuComponents.add(menuComponent); + } + + public void remove(MenuComponent menuComponent) { + menuComponents.remove(menuComponent); + } + + public MenuComponent getChild(int i) { + return (MenuComponent)menuComponents.get(i); + } + + public String getName() { + return name; + } + + public String getDescription() { + return description; + } + + + public Iterator createIterator() { + return new CompositeIterator(menuComponents.iterator()); + } + + + public void print() { + System.out.print("\n" + getName()); + System.out.println(", " + getDescription()); + System.out.println("---------------------"); + + Iterator iterator = menuComponents.iterator(); + while (iterator.hasNext()) { + MenuComponent menuComponent = + (MenuComponent)iterator.next(); + menuComponent.print(); + } + } +} diff --git a/composite/java/headfirst/composite/menuiterator/MenuComponent.java b/composite/java/headfirst/composite/menuiterator/MenuComponent.java new file mode 100755 index 0000000..5b5c278 --- /dev/null +++ b/composite/java/headfirst/composite/menuiterator/MenuComponent.java @@ -0,0 +1,35 @@ +package headfirst.composite.menuiterator; + +import java.util.*; + +public abstract class MenuComponent { + + public void add(MenuComponent menuComponent) { + throw new UnsupportedOperationException(); + } + public void remove(MenuComponent menuComponent) { + throw new UnsupportedOperationException(); + } + public MenuComponent getChild(int i) { + throw new UnsupportedOperationException(); + } + + public String getName() { + throw new UnsupportedOperationException(); + } + public String getDescription() { + throw new UnsupportedOperationException(); + } + public double getPrice() { + throw new UnsupportedOperationException(); + } + public boolean isVegetarian() { + throw new UnsupportedOperationException(); + } + + public abstract Iterator createIterator(); + + public void print() { + throw new UnsupportedOperationException(); + } +} diff --git a/composite/java/headfirst/composite/menuiterator/MenuItem.java b/composite/java/headfirst/composite/menuiterator/MenuItem.java new file mode 100755 index 0000000..0cfd39b --- /dev/null +++ b/composite/java/headfirst/composite/menuiterator/MenuItem.java @@ -0,0 +1,55 @@ +package headfirst.composite.menuiterator; + +import java.util.Iterator; +import java.util.ArrayList; + +public class MenuItem extends MenuComponent { + + String name; + String description; + boolean vegetarian; + double price; + + public MenuItem(String name, + String description, + boolean vegetarian, + double price) + { + this.name = name; + this.description = description; + this.vegetarian = vegetarian; + this.price = price; + } + + public String getName() { + return name; + } + + public String getDescription() { + return description; + } + + public double getPrice() { + return price; + } + + public boolean isVegetarian() { + return vegetarian; + } + + public Iterator createIterator() { + return new NullIterator(); + } + + public void print() { + System.out.print(" " + getName()); + if (isVegetarian()) { + System.out.print("(v)"); + } + System.out.println(", " + getPrice()); + System.out.println(" -- " + getDescription()); + } +//vv MenuItemCompositeV2Main +} +//^^ MenuItemCompositeV2Main +//^^ MenuItemCompositeV2 diff --git a/composite/java/headfirst/composite/menuiterator/MenuTestDrive.java b/composite/java/headfirst/composite/menuiterator/MenuTestDrive.java new file mode 100755 index 0000000..0dbeae2 --- /dev/null +++ b/composite/java/headfirst/composite/menuiterator/MenuTestDrive.java @@ -0,0 +1,115 @@ +package headfirst.composite.menuiterator; + +import java.util.*; + +public class MenuTestDrive { + public static void main(String args[]) { + + MenuComponent pancakeHouseMenu = + new Menu("PANCAKE HOUSE MENU", "Breakfast"); + MenuComponent dinerMenu = + new Menu("DINER MENU", "Lunch"); + MenuComponent cafeMenu = + new Menu("CAFE MENU", "Dinner"); + MenuComponent dessertMenu = + new Menu("DESSERT MENU", "Dessert of course!"); + + MenuComponent allMenus = new Menu("ALL MENUS", "All menus combined"); + + allMenus.add(pancakeHouseMenu); + allMenus.add(dinerMenu); + allMenus.add(cafeMenu); + + pancakeHouseMenu.add(new MenuItem( + "K&B's Pancake Breakfast", + "Pancakes with scrambled eggs, and toast", + true, + 2.99)); + pancakeHouseMenu.add(new MenuItem( + "Regular Pancake Breakfast", + "Pancakes with fried eggs, sausage", + false, + 2.99)); + pancakeHouseMenu.add(new MenuItem( + "Blueberry Pancakes", + "Pancakes made with fresh blueberries, and blueberry syrup", + true, + 3.49)); + pancakeHouseMenu.add(new MenuItem( + "Waffles", + "Waffles, with your choice of blueberries or strawberries", + true, + 3.59)); + + dinerMenu.add(new MenuItem( + "Vegetarian BLT", + "(Fakin') Bacon with lettuce & tomato on whole wheat", + true, + 2.99)); + dinerMenu.add(new MenuItem( + "BLT", + "Bacon with lettuce & tomato on whole wheat", + false, + 2.99)); + dinerMenu.add(new MenuItem( + "Soup of the day", + "A bowl of the soup of the day, with a side of potato salad", + false, + 3.29)); + dinerMenu.add(new MenuItem( + "Hotdog", + "A hot dog, with saurkraut, relish, onions, topped with cheese", + false, + 3.05)); + dinerMenu.add(new MenuItem( + "Steamed Veggies and Brown Rice", + "A medly of steamed vegetables over brown rice", + true, + 3.99)); + + dinerMenu.add(new MenuItem( + "Pasta", + "Spaghetti with Marinara Sauce, and a slice of sourdough bread", + true, + 3.89)); + + dinerMenu.add(dessertMenu); + + dessertMenu.add(new MenuItem( + "Apple Pie", + "Apple pie with a flakey crust, topped with vanilla icecream", + true, + 1.59)); + dessertMenu.add(new MenuItem( + "Cheesecake", + "Creamy New York cheesecake, with a chocolate graham crust", + true, + 1.99)); + dessertMenu.add(new MenuItem( + "Sorbet", + "A scoop of raspberry and a scoop of lime", + true, + 1.89)); + + cafeMenu.add(new MenuItem( + "Veggie Burger and Air Fries", + "Veggie burger on a whole wheat bun, lettuce, tomato, and fries", + true, + 3.99)); + cafeMenu.add(new MenuItem( + "Soup of the day", + "A cup of the soup of the day, with a side salad", + false, + 3.69)); + cafeMenu.add(new MenuItem( + "Burrito", + "A large burrito, with whole pinto beans, salsa, guacamole", + true, + 4.29)); + + Waitress waitress = new Waitress(allMenus); + + waitress.printVegetarianMenu(); + + } +} diff --git a/composite/java/headfirst/composite/menuiterator/NullIterator.java b/composite/java/headfirst/composite/menuiterator/NullIterator.java new file mode 100755 index 0000000..d4f15c5 --- /dev/null +++ b/composite/java/headfirst/composite/menuiterator/NullIterator.java @@ -0,0 +1,18 @@ +package headfirst.composite.menuiterator; + +import java.util.Iterator; + +public class NullIterator implements Iterator { + + public Object next() { + return null; + } + + public boolean hasNext() { + return false; + } + + public void remove() { + throw new UnsupportedOperationException(); + } +} diff --git a/composite/java/headfirst/composite/menuiterator/Waitress.java b/composite/java/headfirst/composite/menuiterator/Waitress.java new file mode 100755 index 0000000..4a66210 --- /dev/null +++ b/composite/java/headfirst/composite/menuiterator/Waitress.java @@ -0,0 +1,30 @@ +package headfirst.composite.menuiterator; + +import java.util.Iterator; + +public class Waitress { + MenuComponent allMenus; + + public Waitress(MenuComponent allMenus) { + this.allMenus = allMenus; + } + + public void printMenu() { + allMenus.print(); + } + + public void printVegetarianMenu() { + Iterator iterator = allMenus.createIterator(); + + System.out.println("\nVEGETARIAN MENU\n----"); + while (iterator.hasNext()) { + MenuComponent menuComponent = + (MenuComponent)iterator.next(); + try { + if (menuComponent.isVegetarian()) { + menuComponent.print(); + } + } catch (UnsupportedOperationException e) {} + } + } +} diff --git a/composite/menu.py b/composite/menu.py new file mode 100644 index 0000000..18bde2a --- /dev/null +++ b/composite/menu.py @@ -0,0 +1,71 @@ +from menuabc import MenuComponent + + +class Menu(MenuComponent): + + def __init__(self, name, description): + self.menu_components = [] + self.__name = name + self.__description = description + + def append(self, menu_component): + self.menu_components.append(menu_component) + + def remove(self, menu_component): + self.menu_components.remove(menu_component) + + def __getitem__(self, index): + return self.menu_components[index] + + @property + def name(self): + return self.__name + + @property + def description(self): + return self.__description + + @property + def is_vegetarian(self): + return all(sub_menu.is_vegetarian for sub_menu in self.menu_components) + + def display(self): + print('\n' + self.name, end='') + if self.is_vegetarian: + print(' (v)', end='') + print(', ', self.description, sep='') + print('-' * 60) + for menu_component in self.menu_components: + menu_component.display() + + +class MenuItem(MenuComponent): + + def __init__(self, name, description, vegetarian, price): + self.__name = name + self.__description = description + self.__vegetarian = vegetarian + self.__price = price + + @property + def name(self): + return self.__name + + @property + def description(self): + return self.__description + + @property + def is_vegetarian(self): + return self.__vegetarian + + @property + def price(self): + return self.__price + + def display(self): + print(' ' + self.name, end='') + if self.is_vegetarian: + print(' (v)', end='') + print(',', self.price) + print(' --', self.description) diff --git a/composite/menuabc.py b/composite/menuabc.py new file mode 100644 index 0000000..141b080 --- /dev/null +++ b/composite/menuabc.py @@ -0,0 +1,41 @@ +from abc import ABC, abstractmethod + + +class MenuComponent(ABC): + '''Item do cardápio, com sub-itens''' + + def append(self, item): + '''incluir sub-item''' + raise NotImplementedError() + + def remove(self, item): + '''remover sub-item''' + raise NotImplementedError() + + def __getitem__(self, i): + '''obter sub-item pelo índice''' + raise NotImplementedError() + + @property + @abstractmethod + def name(self): + '''obter nome do item''' + + @property + @abstractmethod + def description(self): + '''obter descrição do item''' + + @property + def price(self): + '''obter preço do item''' + raise NotImplementedError() + + @property + @abstractmethod + def is_vegetarian(self): + '''obter booleano indicador de item vegetariano''' + + @abstractmethod + def display(self): + '''exibir item e sub-itens''' diff --git a/decorator/clockdeco.py b/decorator/clockdeco.py new file mode 100644 index 0000000..4010c14 --- /dev/null +++ b/decorator/clockdeco.py @@ -0,0 +1,16 @@ +# clockdeco.py + +import time +import functools + +def clock(func): + @functools.wraps(func) + def clocked(*args): + t0 = time.time() + result = func(*args) + elapsed = time.time() - t0 + name = func.__name__ + arg_str = ', '.join(repr(arg) for arg in args) + print('[%0.8fs] %s(%s) -> %r' % (elapsed, name, arg_str, result)) + return result + return clocked diff --git a/decorator/clockdeco_cls.py b/decorator/clockdeco_cls.py new file mode 100644 index 0000000..5b19119 --- /dev/null +++ b/decorator/clockdeco_cls.py @@ -0,0 +1,43 @@ +# clockdeco_param.py + +""" +>>> snooze(.1) # doctest: +ELLIPSIS +[0.101...s] snooze(0.1) -> None +>>> clock('{name}: {elapsed}')(time.sleep)(.2) # doctest: +ELLIPSIS +sleep: 0.20... +>>> clock('{name}({args}) dt={elapsed:0.3f}s')(time.sleep)(.2) +sleep(0.2) dt=0.201s +""" + +# BEGIN CLOCKDECO_CLS +import time + +DEFAULT_FMT = '[{elapsed:0.8f}s] {name}({args}) -> {result}' + +class clock: + + def __init__(self, fmt=DEFAULT_FMT): + self.fmt = fmt + + def __call__(self, func): + def clocked(*_args): + t0 = time.time() + _result = func(*_args) + elapsed = time.time() - t0 + name = func.__name__ + args = ', '.join(repr(arg) for arg in _args) + result = repr(_result) + print(self.fmt.format(**locals())) + return _result + return clocked + +if __name__ == '__main__': + + @clock() + def snooze(seconds): + time.sleep(seconds) + + for i in range(3): + snooze(.123) + +# END CLOCKDECO_CLS diff --git a/decorator/clockdeco_demo.py b/decorator/clockdeco_demo.py new file mode 100644 index 0000000..f869e23 --- /dev/null +++ b/decorator/clockdeco_demo.py @@ -0,0 +1,18 @@ +# clockdeco_demo.py + +import time +from clockdeco import clock + +@clock +def snooze(seconds): + time.sleep(seconds) + +@clock +def factorial(n): + return 1 if n < 2 else n*factorial(n-1) + +if __name__=='__main__': + print('*' * 40, 'Calling snooze(.123)') + snooze(.123) + print('*' * 40, 'Calling factorial(6)') + print('6! =', factorial(6)) diff --git a/decorator/clockdeco_param.py b/decorator/clockdeco_param.py new file mode 100644 index 0000000..1f7c076 --- /dev/null +++ b/decorator/clockdeco_param.py @@ -0,0 +1,40 @@ +# clockdeco_param.py + +""" +>>> snooze(.1) # doctest: +ELLIPSIS +[0.101...s] snooze(0.1) -> None +>>> clock('{name}: {elapsed}')(time.sleep)(.2) # doctest: +ELLIPSIS +sleep: 0.20... +>>> clock('{name}({args}) dt={elapsed:0.3f}s')(time.sleep)(.2) +sleep(0.2) dt=0.201s +""" + +# BEGIN CLOCKDECO_PARAM +import time + +DEFAULT_FMT = '[{elapsed:0.8f}s] {name}({args}) -> {result}' + +def clock(fmt=DEFAULT_FMT): # <1> + def decorate(func): # <2> + def clocked(*_args): # <3> + t0 = time.time() + _result = func(*_args) # <4> + elapsed = time.time() - t0 + name = func.__name__ + args = ', '.join(repr(arg) for arg in _args) # <5> + result = repr(_result) # <6> + print(fmt.format(**locals())) # <7> + return _result # <8> + return clocked # <9> + return decorate # <10> + +if __name__ == '__main__': + + @clock() # <11> + def snooze(seconds): + time.sleep(seconds) + + for i in range(3): + snooze(.123) + +# END CLOCKDECO_PARAM diff --git a/decorator/clockdeco_param_demo1.py b/decorator/clockdeco_param_demo1.py new file mode 100644 index 0000000..053d275 --- /dev/null +++ b/decorator/clockdeco_param_demo1.py @@ -0,0 +1,9 @@ +import time +from clockdeco_param import clock + +@clock('{name}: {elapsed}s') +def snooze(seconds): + time.sleep(seconds) + +for i in range(3): + snooze(.123) diff --git a/decorator/clockdeco_param_demo2.py b/decorator/clockdeco_param_demo2.py new file mode 100644 index 0000000..df394f9 --- /dev/null +++ b/decorator/clockdeco_param_demo2.py @@ -0,0 +1,9 @@ +import time +from clockdeco_param import clock + +@clock('{name}({args}) dt={elapsed:0.3f}s') +def snooze(seconds): + time.sleep(seconds) + +for i in range(3): + snooze(.123) diff --git a/decorator/registration.py b/decorator/registration.py new file mode 100644 index 0000000..a025233 --- /dev/null +++ b/decorator/registration.py @@ -0,0 +1,27 @@ +registry = [] # <1> + +def register(func): # <2> + print('running register(%s)' % func) # <3> + registry.append(func) # <4> + return func # <5> + +@register # <6> +def f1(): + print('running f1()') + +@register +def f2(): + print('running f2()') + +def f3(): # <7> + print('running f3()') + +def main(): # <8> + print('running main()') + print('registry ->', registry) + f1() + f2() + f3() + +if __name__=='__main__': + main() # <9> diff --git a/figuras/1280px-Lego_dimensions.svg.png b/figuras/1280px-Lego_dimensions.svg.png new file mode 100644 index 0000000..d4547ad Binary files /dev/null and b/figuras/1280px-Lego_dimensions.svg.png differ diff --git a/figuras/RelacaoEntreDesignPatterns.png b/figuras/RelacaoEntreDesignPatterns.png new file mode 100644 index 0000000..08d2398 Binary files /dev/null and b/figuras/RelacaoEntreDesignPatterns.png differ diff --git a/figuras/RelacaoEntreDesignPatterns.xcf b/figuras/RelacaoEntreDesignPatterns.xcf new file mode 100644 index 0000000..e13a2ec Binary files /dev/null and b/figuras/RelacaoEntreDesignPatterns.xcf differ diff --git a/figuras/polly-blocos-estrela-caixa.jpg b/figuras/polly-blocos-estrela-caixa.jpg new file mode 100644 index 0000000..4678ce0 Binary files /dev/null and b/figuras/polly-blocos-estrela-caixa.jpg differ diff --git a/novatec/adaptador.py b/novatec/adaptador.py new file mode 100644 index 0000000..8358664 --- /dev/null +++ b/novatec/adaptador.py @@ -0,0 +1,37 @@ + +""" + + >>> p = Parede() + >>> f = Fonte(p) + >>> a = Aparelho(f) + >>> a.ligar() + >>> a.ligado + True + +""" + +class Aparelho: + + def __init__(self, fonte=None): + self.fonte = fonte + self.ligado = False + + def ligar(self): + if self.fonte.corrente_continua(): + self.ligado = True + +class Parede: + + def corrente_alternada(self): + return True + +class Fonte: + + def __init__(self, parede): + self.parede = parede + + def converter(self): + return self.parede.corrente_alternada() + + def corrente_continua(self): + return self.converter() diff --git a/novatec/borg.py b/novatec/borg.py new file mode 100644 index 0000000..9738e4a --- /dev/null +++ b/novatec/borg.py @@ -0,0 +1,8 @@ + +class Borg: + _estado_compartilhado = {} + + def __new__(cls, *args, **kwargs): + novo_borg = super().__new__(cls, *args, **kwargs) + novo_borg.__dict__ = cls._estado_compartilhado + return novo_borg diff --git a/novatec/classic_strategy.py b/novatec/classic_strategy.py new file mode 100644 index 0000000..0d8f7d6 --- /dev/null +++ b/novatec/classic_strategy.py @@ -0,0 +1,106 @@ +# classic_strategy.py +# Strategy pattern -- classic implementation + +""" +# BEGIN CLASSIC_STRATEGY_TESTS + + >>> joe = Customer('John Doe', 0) # <1> + >>> ann = Customer('Ann Smith', 1100) + >>> cart = [LineItem('banana', 4, .5), # <2> + ... LineItem('apple', 10, 1.5), + ... LineItem('watermellon', 5, 5.0)] + >>> Order(joe, cart, FidelityPromo()) # <3> + + >>> Order(ann, cart, FidelityPromo()) # <4> + + >>> banana_cart = [LineItem('banana', 30, .5), # <5> + ... LineItem('apple', 10, 1.5)] + >>> Order(joe, banana_cart, BulkItemPromo()) # <6> + + >>> long_order = [LineItem(str(item_code), 1, 1.0) # <7> + ... for item_code in range(10)] + >>> Order(joe, long_order, LargeOrderPromo()) # <8> + + >>> Order(joe, cart, LargeOrderPromo()) + + +# END CLASSIC_STRATEGY_TESTS +""" +# BEGIN CLASSIC_STRATEGY + +from abc import ABC, abstractmethod +from collections import namedtuple + +Customer = namedtuple('Customer', 'name fidelity') + + +class LineItem: + + def __init__(self, product, quantity, price): + self.product = product + self.quantity = quantity + self.price = price + + def total(self): + return self.price * self.quantity + + +class Order: # the Context + + def __init__(self, customer, cart, promotion=None): + self.customer = customer + self.cart = list(cart) + self.promotion = promotion + + def total(self): + if not hasattr(self, '__total'): + self.__total = sum(item.total() for item in self.cart) + return self.__total + + def due(self): + if self.promotion is None: + discount = 0 + else: + discount = self.promotion.discount(self) + return self.total() - discount + + def __repr__(self): + fmt = '' + return fmt.format(self.total(), self.due()) + + +class Promotion(ABC): # the Strategy: an Abstract Base Class + + @abstractmethod + def discount(self, order): + """Return discount as a positive dollar amount""" + + +class FidelityPromo(Promotion): # first Concrete Strategy + """5% discount for customers with 1000 or more fidelity points""" + + def discount(self, order): + return order.total() * .05 if order.customer.fidelity >= 1000 else 0 + + +class BulkItemPromo(Promotion): # second Concrete Strategy + """10% discount for each LineItem with 20 or more units""" + + def discount(self, order): + discount = 0 + for item in order.cart: + if item.quantity >= 20: + discount += item.total() * .1 + return discount + + +class LargeOrderPromo(Promotion): # third Concrete Strategy + """7% discount for orders with 10 or more distinct items""" + + def discount(self, order): + distinct_items = {item.product for item in order.cart} + if len(distinct_items) >= 10: + return order.total() * .07 + return 0 + +# END CLASSIC_STRATEGY diff --git a/novatec/composite.py b/novatec/composite.py new file mode 100644 index 0000000..c19e194 --- /dev/null +++ b/novatec/composite.py @@ -0,0 +1,115 @@ +r""" +Exemplo do padão Composite +========================== + +Aplicação: catálogo de produtos eletrônicos para hobistas. + +A *Folha* de um catálogo é a classe ``Componente``:: + + >>> ard = Componente(1, 'Arduino Uno', 79.95) + >>> ard + Componente(sku=1, nome='Arduino Uno', preco=79.95) + >>> ard.preco + 79.95 + >>> bobs = Componente(2, "Bob's Duino", 59.95) + >>> bobs + Componente(sku=2, nome="Bob's Duino", preco=59.95) + >>> str(ard) + 'Arduino Uno (#1)\t$79.95' + >>> cabo_usb = Componente(3, 'Cabo USB azul 1m', 4.95) + +Um catálogo é uma lista de produtos, que pode ser exibida formatada:: + + >>> cat = Catalogo([ard, bobs, cabo_usb]) + >>> cat + + >>> print(cat) # doctest: +NORMALIZE_WHITESPACE + Arduino Uno (#1) $79.95 + Bob's Duino (#2) $59.95 + Cabo USB azul 1m (#3) $4.95 + +Um kit é um produto formato por mais de um componente. + + >>> kit1 = Kit(4, 'Arduino completíssimo', [ard, cabo_usb]) + >>> kit1 + + +Por padrão, o preço do kit é soma dos preços dos componentes incluídos. + + >>> kit1.preco + 84.9 + +A apresentação do kit inclui o nome dos componentes:: + + >>> print(kit1) # doctest: +NORMALIZE_WHITESPACE + Arduino completíssimo (#4) $84.90 + - Arduino Uno + - Cabo USB azul 1m + +Na listagem do catálogo, os componentes dos kits também aparecem:: + + >>> cat = Catalogo([kit1, ard, bobs, cabo_usb]) + >>> print(cat) # doctest: +NORMALIZE_WHITESPACE + Arduino completíssimo (#4) $84.90 + - Arduino Uno + - Cabo USB azul 1m + Arduino Uno (#1) $79.95 + Bob's Duino (#2) $59.95 + Cabo USB azul 1m (#3) $4.95 + +""" + +class Componente: + """Classe *Folha* (*Leaf*) no design pattern""" + + def __init__(self, sku, nome, preco): + self.sku = sku + self.nome = nome + self.preco = preco + + def __repr__(self): + return "Componente(sku={}, nome={!r}, preco={})".format( + self.sku, self.nome, self.preco) + + def __str__(self): + return '{} (#{})\t${}'.format(self.nome, self.sku, self.preco) + + +class Kit: + """Classe *Composite* no design pattern""" + + def __init__(self, sku, nome, produtos): + self.sku = sku + self.nome = nome + self.produtos = list(produtos) + + def __repr__(self): + return ''.format( + self.sku, self.nome, len(self.produtos)) + + def __str__(self): + saida = ['{} (#{})\t${:0.2f}'.format(self.nome, self.sku, self.preco)] + for produto in self.produtos: + saida.append('- {}'.format(produto.nome)) + return '\n'.join(saida) + + + @property + def preco(self): + return sum(p.preco for p in self.produtos) + + +class Catalogo: + """Classe *Cliente* no design pattern""" + + def __init__(self, produtos): + self.produtos = list(produtos) + + def __repr__(self): + return ''.format(len(self.produtos)) + + def __str__(self): + saida = [] + for produto in self.produtos: + saida.append(str(produto)) + return '\n'.join(saida) diff --git a/novatec/observer.py b/novatec/observer.py new file mode 100644 index 0000000..e6b35d4 --- /dev/null +++ b/novatec/observer.py @@ -0,0 +1,51 @@ +""" + + >>> p = PesquisaEleitoral() + >>> p.adicionar_votos('A', 5) + 5 votos adicionados para 'A' + >>> p.adicionar_votos('B', 3) + 3 votos adicionados para 'B' + +Observadores em ação:: + + >>> p.vincular_observador(estatistica) + >>> p.adicionar_votos('A', 2) + 2 votos adicionados para 'A' + Estatística: A 70%, B 30% + >>> p.vincular_observador(grafico) + >>> p.adicionar_votos('B', 7) + 7 votos adicionados para 'B' + Estatística: A 41%, B 59% + A ############################ + B ######################################### +""" + +class PesquisaEleitoral: + """Classe *Sujeito* do padrão *Observer*""" + + def __init__(self): + self.votos = {'A':0, 'B':0} + self.observadores = [] + + def adicionar_votos(self, candidato, votos): + print('{} votos adicionados para {!r}'.format(votos, candidato)) + self.votos[candidato] += votos + self.notificar_observadores() + + def vincular_observador(self, observador): + self.observadores.append(observador) + + def notificar_observadores(self): + for obs in self.observadores: + obs(self) + +def estatistica(pesquisa): + total = sum(pesquisa.votos.values()) + pct_a = pesquisa.votos['A'] / total * 100 + pct_b = pesquisa.votos['B'] / total * 100 + print('Estatística: A {:0.0f}%, B {:0.0f}%'.format(pct_a, pct_b)) + +def grafico(pesquisa): + total = sum(pesquisa.votos.values()) + print('A', '#' * int(pesquisa.votos['A'] / total * 70)) + print('B', '#' * int(pesquisa.votos['B'] / total * 70)) diff --git a/novatec/saobernardo.py b/novatec/saobernardo.py new file mode 100644 index 0000000..d200092 --- /dev/null +++ b/novatec/saobernardo.py @@ -0,0 +1,31 @@ + +class Barril: + + def __init__(self, doses=10): + self.doses = doses + + def servir(self): + if self.doses > 0: + return '1 dose' + else: + raise LookupError('barril vazio') + + def encher(self, doses): + self.doses += doses + +class SaoBernardo: + + def __init__(self, nome): + self.nome = nome + self._barril = Barril() + + def servir(self): + dose = self._barril.servir() + print('{} servindo: {}'.format(self.nome, dose)) + return dose + + def __getattr__(self, nome): + if hasattr(self._barril, nome): + return getattr(self._barril, nome) + else: + raise AttributeError('atributo desconhecido') diff --git a/novatec/singleton.py b/novatec/singleton.py new file mode 100644 index 0000000..9dae6a2 --- /dev/null +++ b/novatec/singleton.py @@ -0,0 +1,8 @@ + +class Singleton: + + def __new__(cls, *args, **kwargs): + if not hasattr(cls, '_a_instancia'): + cls._a_instancia = super().__new__(cls, *args, **kwargs) + + return cls._a_instancia diff --git a/novatec/strategy.py b/novatec/strategy.py new file mode 100644 index 0000000..88e3340 --- /dev/null +++ b/novatec/strategy.py @@ -0,0 +1,106 @@ +# classic_strategy.py +# Strategy pattern -- classic implementation + +""" +# BEGIN CLASSIC_STRATEGY_TESTS + + >>> joe = Customer('John Doe', 0) # <1> + >>> ann = Customer('Ann Smith', 1100) + >>> cart = [LineItem('banana', 4, .5), # <2> + ... LineItem('apple', 10, 1.5), + ... LineItem('watermellon', 5, 5.0)] + >>> Order(joe, cart, fidelity_promo) # <3> + + >>> Order(ann, cart, FidelityPromo()) # <4> + + >>> banana_cart = [LineItem('banana', 30, .5), # <5> + ... LineItem('apple', 10, 1.5)] + >>> Order(joe, banana_cart, BulkItemPromo()) # <6> + + >>> long_order = [LineItem(str(item_code), 1, 1.0) # <7> + ... for item_code in range(10)] + >>> Order(joe, long_order, LargeOrderPromo()) # <8> + + >>> Order(joe, cart, LargeOrderPromo()) + + +# END CLASSIC_STRATEGY_TESTS +""" +# BEGIN CLASSIC_STRATEGY + +from abc import ABC, abstractmethod +from collections import namedtuple + +Customer = namedtuple('Customer', 'name fidelity') + + +class LineItem: + + def __init__(self, product, quantity, price): + self.product = product + self.quantity = quantity + self.price = price + + def total(self): + return self.price * self.quantity + + +class Order: # the Context + + def __init__(self, customer, cart, promotion=None): + self.customer = customer + self.cart = list(cart) + self.promotion = promotion + + def total(self): + if not hasattr(self, '__total'): + self.__total = sum(item.total() for item in self.cart) + return self.__total + + def due(self): + if self.promotion is None: + discount = 0 + else: + discount = self.promotion.discount(self) + return self.total() - discount + + def __repr__(self): + fmt = '' + return fmt.format(self.total(), self.due()) + + +class Promotion(ABC): # the Strategy: an Abstract Base Class + + @abstractmethod + def discount(self, order): + """Return discount as a positive dollar amount""" + + +class FidelityPromo(Promotion): # first Concrete Strategy + """5% discount for customers with 1000 or more fidelity points""" + + def discount(self, order): + return order.total() * .05 if order.customer.fidelity >= 1000 else 0 + + +class BulkItemPromo(Promotion): # second Concrete Strategy + """10% discount for each LineItem with 20 or more units""" + + def discount(self, order): + discount = 0 + for item in order.cart: + if item.quantity >= 20: + discount += item.total() * .1 + return discount + + +class LargeOrderPromo(Promotion): # third Concrete Strategy + """7% discount for orders with 10 or more distinct items""" + + def discount(self, order): + distinct_items = {item.product for item in order.cart} + if len(distinct_items) >= 10: + return order.total() * .07 + return 0 + +# END CLASSIC_STRATEGY diff --git a/novatec/tombola.py b/novatec/tombola.py new file mode 100644 index 0000000..8244e13 --- /dev/null +++ b/novatec/tombola.py @@ -0,0 +1,85 @@ +""" +Tômbola é uma estrutura que permite a retirada de itens de uma lista em +ordem aleatória sem repetição. + +A tômbola é criada passando um iterável de itens:: + + >>> colecao = 'ABCD' + >>> t = Tombola(colecao) + +A tômbola pode começar vazia:: + + >>> t0 = Tombola() + +Para saber se a tômbola está carregada, existe um método:: + + >>> t.carregada() + True + >>> t0.carregada() + False + +Os itens são retirados um a um:: + + >>> item = t.retirar() + >>> item in colecao + True + +Uma vez sorteado, o item é retirado e quando todos são retirados +a tômbola fica vazia:: + + >>> for i in range(3): + ... _ = t.retirar() + ... + >>> t.carregada() + False + +Se os itens forem misturados, espera-se que não sejam retirados na mesma +ordem em que foram colocados (de acordo com as proabilidades):: + + >>> qt_itens = 20 + >>> t = Tombola(range(qt_itens)) + >>> t.misturar() + >>> retirados = [] + >>> for i in range(qt_itens): + ... retirados.append(t.retirar()) + ... + >>> retirados != list(range(qt_itens)) + True + +Para a conveniência do usuário, a instância de Tômbola pode ser invocada diretamente +para retirar um item:: + + >>> um_a_tres = [1, 2, 3] + >>> t = Tombola(um_a_tres) + >>> sorteado = t() + >>> sorteado in um_a_tres + True + + + + +""" + +from random import shuffle + +class Tombola: + """Armazena e devolve itens sorteados sem repetir""" + + def __init__(self, itens=None): + if itens is None: + self.itens = [] + else: + self.itens = list(itens) + + def carregada(self): + return bool(self.itens) + + def retirar(self): + return self.itens.pop() + + def misturar(self): + shuffle(self.itens) + + def __call__(self): + return self.retirar() + diff --git a/novatec/trem.py b/novatec/trem.py new file mode 100644 index 0000000..8e56c1c --- /dev/null +++ b/novatec/trem.py @@ -0,0 +1,59 @@ +""" +Um trem é um iterável com vagões. + +O construtor pede o número de vagões:: + + >>> t = Trem(3) + +O trem é um iterável:: + + >>> it = iter(t) + +E o iterador obtido devolve vagões:: + + >>> next(it) + 'vagão #1' + >>> next(it) + 'vagão #2' + >>> next(it) + 'vagão #3' + +Somente a quantidade correta de vagões é devolvida:: + + >>> next(it) + Traceback (most recent call last): + ... + StopIteration + +Finalmente, podemos percorrer um trem num laço ``for``:: + + >>> for vagao in Trem(3): + ... print(vagao) + ... + vagão #1 + vagão #2 + vagão #3 + + +""" + +class Trem: + + def __init__(self, qt_vagoes): + self.qt_vagoes = qt_vagoes + + def __iter__(self): + return IteradorTrem(self) + + +class IteradorTrem: + + def __init__(self, trem): + self.qt_vagoes = trem.qt_vagoes + self.vagao_atual = 0 + + def __next__(self): + self.vagao_atual += 1 + if self.vagao_atual > self.qt_vagoes: + raise StopIteration() + return 'vagão #{}'.format(self.vagao_atual) diff --git a/novatec/trem_iter.py b/novatec/trem_iter.py new file mode 100644 index 0000000..7dae3c2 --- /dev/null +++ b/novatec/trem_iter.py @@ -0,0 +1,47 @@ +""" +Um trem é um iterável com vagões. + +O construtor pede o número de vagões:: + + >>> t = Trem(3) + +O trem é um iterável:: + + >>> it = iter(t) + +E o iterador obtido devolve vagões:: + + >>> next(it) + 'vagão #1' + >>> next(it) + 'vagão #2' + >>> next(it) + 'vagão #3' + +Somente a quantidade correta de vagões é devolvida:: + + >>> next(it) + Traceback (most recent call last): + ... + StopIteration + +Finalmente, podemos percorrer um trem num laço ``for``:: + + >>> for vagao in Trem(3): + ... print(vagao) + ... + vagão #1 + vagão #2 + vagão #3 + + +""" + +class Trem: + + def __init__(self, qt_vagoes): + self.qt_vagoes = qt_vagoes + + def __iter__(self): + for i in range(self.qt_vagoes): + yield 'vagão #{}'.format(i + 1) diff --git a/novatec/trem_seq.py b/novatec/trem_seq.py new file mode 100644 index 0000000..21b5460 --- /dev/null +++ b/novatec/trem_seq.py @@ -0,0 +1,53 @@ +""" +Um trem é um iterável com vagões. + +O construtor pede o número de vagões:: + + >>> t = Trem(3) + +O trem é um iterável:: + + >>> it = iter(t) + +E o iterador obtido devolve vagões:: + + >>> next(it) + 'vagão #1' + >>> next(it) + 'vagão #2' + >>> next(it) + 'vagão #3' + +Somente a quantidade correta de vagões é devolvida:: + + >>> next(it) + Traceback (most recent call last): + ... + StopIteration + +Finalmente, podemos percorrer um trem num laço ``for``:: + + >>> for vagao in Trem(3): + ... print(vagao) + ... + vagão #1 + vagão #2 + vagão #3 + +Neste caso, cada vagão pode ser acessado por um índice: + + >>> t[1] + 'vagão #2' + +""" + +class Trem: + + def __init__(self, qt_vagoes): + self.qt_vagoes = qt_vagoes + + def __getitem__(self, indice): + if indice < self.qt_vagoes: + return 'vagão #{}'.format(indice + 1) + else: + raise IndexError('não há mais vagões') diff --git a/observer/README.md b/observer/README.md new file mode 100644 index 0000000..810ad5a --- /dev/null +++ b/observer/README.md @@ -0,0 +1,4 @@ +# Observer pattern + +Code example adapted from page 334 in chapter 14, *Useful Design Patterns* of the book [Expert Python Programming](https://www.packtpub.com/application-development/expert-python-programming) (Packt, 2008) by **Tarek Ziadé**. + diff --git a/observer/observer.py b/observer/observer.py new file mode 100644 index 0000000..253c3d4 --- /dev/null +++ b/observer/observer.py @@ -0,0 +1,55 @@ +""" + >>> WriteEvent.register(log) + >>> WriteEvent.notify('file #1') + file #1 was written + >>> WriteEvent.register(AnotherObserver()) + >>> WriteEvent.notify('file #2') + file #2 was written + Yeah WriteEvent told me! + +""" + + +class Event(object): + _observers = [] + + def __init__(self, subject): + self.subject = subject + + @classmethod + def register(cls, observer): + if observer not in cls._observers: + cls._observers.append(observer) + + @classmethod + def unregister(cls, observer): + if observer in cls._observers: + self._observers.remove(observer) + + @classmethod + def notify(cls, subject): + event = cls(subject) + for observer in cls._observers: + observer(event) + +class WriteEvent(Event): + + def __repr__(self): + return 'WriteEvent' + +def log(event): + print('%s was written' % event.subject) + + +class AnotherObserver(object): + def __call__(self, event): + print('Yeah %s told me!' % event) + +if __name__ == '__main__': + import doctest + failed, attempted = doctest.testmod() + if not failed: + print('OK: %s tests passed' % attempted) + else: + print('FAIL: %s test(s) failed (of %s total)' + % (failed, attempted)) diff --git a/strategy/README.rst b/strategy/README.rst new file mode 100644 index 0000000..7ebe7dc --- /dev/null +++ b/strategy/README.rst @@ -0,0 +1,4 @@ +Sample code for Chapter 6 - "Design patterns with first class functions" + +From the book "Fluent Python" by Luciano Ramalho (O'Reilly, 2015) +http://shop.oreilly.com/product/0636920032519.do diff --git a/strategy/classic_strategy.py b/strategy/classic_strategy.py new file mode 100644 index 0000000..0d8f7d6 --- /dev/null +++ b/strategy/classic_strategy.py @@ -0,0 +1,106 @@ +# classic_strategy.py +# Strategy pattern -- classic implementation + +""" +# BEGIN CLASSIC_STRATEGY_TESTS + + >>> joe = Customer('John Doe', 0) # <1> + >>> ann = Customer('Ann Smith', 1100) + >>> cart = [LineItem('banana', 4, .5), # <2> + ... LineItem('apple', 10, 1.5), + ... LineItem('watermellon', 5, 5.0)] + >>> Order(joe, cart, FidelityPromo()) # <3> + + >>> Order(ann, cart, FidelityPromo()) # <4> + + >>> banana_cart = [LineItem('banana', 30, .5), # <5> + ... LineItem('apple', 10, 1.5)] + >>> Order(joe, banana_cart, BulkItemPromo()) # <6> + + >>> long_order = [LineItem(str(item_code), 1, 1.0) # <7> + ... for item_code in range(10)] + >>> Order(joe, long_order, LargeOrderPromo()) # <8> + + >>> Order(joe, cart, LargeOrderPromo()) + + +# END CLASSIC_STRATEGY_TESTS +""" +# BEGIN CLASSIC_STRATEGY + +from abc import ABC, abstractmethod +from collections import namedtuple + +Customer = namedtuple('Customer', 'name fidelity') + + +class LineItem: + + def __init__(self, product, quantity, price): + self.product = product + self.quantity = quantity + self.price = price + + def total(self): + return self.price * self.quantity + + +class Order: # the Context + + def __init__(self, customer, cart, promotion=None): + self.customer = customer + self.cart = list(cart) + self.promotion = promotion + + def total(self): + if not hasattr(self, '__total'): + self.__total = sum(item.total() for item in self.cart) + return self.__total + + def due(self): + if self.promotion is None: + discount = 0 + else: + discount = self.promotion.discount(self) + return self.total() - discount + + def __repr__(self): + fmt = '' + return fmt.format(self.total(), self.due()) + + +class Promotion(ABC): # the Strategy: an Abstract Base Class + + @abstractmethod + def discount(self, order): + """Return discount as a positive dollar amount""" + + +class FidelityPromo(Promotion): # first Concrete Strategy + """5% discount for customers with 1000 or more fidelity points""" + + def discount(self, order): + return order.total() * .05 if order.customer.fidelity >= 1000 else 0 + + +class BulkItemPromo(Promotion): # second Concrete Strategy + """10% discount for each LineItem with 20 or more units""" + + def discount(self, order): + discount = 0 + for item in order.cart: + if item.quantity >= 20: + discount += item.total() * .1 + return discount + + +class LargeOrderPromo(Promotion): # third Concrete Strategy + """7% discount for orders with 10 or more distinct items""" + + def discount(self, order): + distinct_items = {item.product for item in order.cart} + if len(distinct_items) >= 10: + return order.total() * .07 + return 0 + +# END CLASSIC_STRATEGY diff --git a/template-method/introspect.py b/template-method/introspect.py new file mode 100644 index 0000000..0ae456c --- /dev/null +++ b/template-method/introspect.py @@ -0,0 +1,24 @@ + +# EAFP -> It's Easier to Ask Forgiveness than Permission +def docmd(self, cmd, a): + #... + try: + fn = getattr(self, 'do_' + cmd) + except AttributeError: + return self.dodefault(cmd, a) + return fn(a) + + +def docmd(self, cmd, a): + #... + return getattr(self, 'do_' + cmd, self.dodefault)(a) + + +# LBYL -> Look Before You Leap +def docmd(self, cmd, a): + #... + if hasattr(self, 'do_' + cmd): + fn = getattr(self, 'do_' + cmd) + return fn(a) + else: + return self.dodefault(cmd, a)