amanagomez
Posts by :
Interface Segregation Principle
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 |
public interface IVehicleMaintenance { void changeWheel(int number); void changeEngineOil(); void lubeChain(); void checkBattery(); } public class CarWorkshop implements IVehicleMaintenace { @Override public void changeWheel(int number) { // ... } @Override public void changeEngineOil() { // ... } @Override public void lubeChain() { throw new UnsupportedOperationException("No chain to lube in cars"); } @Override public void checkBattery() { // ... } } public class BikeWorkshop implements IVehicleMaintenace { @Override public void changeWheel(int number) { // ... } @Override public void changeEngineOil() { throw new UnsupportedOperationException("No engine oil to change in bikes"); } @Override public void lubeChain() { // ... } @Override public void checkBattery() { // only valid for electric bikes.. } } /* * Same happens for all other types of vehicles. * Even for the changeWheel operation, which looks more common, it does not * apply to boats, and it may behave differently in different vehicles with * different number and types of wheels. * * In fact, throwing an UnsupportedOperationException in the CarMaintenance * and BikeMaintenance classes is a violation of the LSP as the superclass * (interface in this case) is not replaceable by the subclasses. * * Moreover, with this solution we would be breaking the Open/Closed Principle * (OCP), which states that a class should be open for extension but closed * for modification. This is so because, in case of adding a new type of * vehicle, we would have to modify VehicleMaintenace to add the new types * of maintenance operations, and then we would have to modify all subclasses * to add the new operations even if they would not apply to the specific * vehicle represented by a particular subclass. * */ //////////////////////////////////////////////////////////////////// /* * A simple solution is to separate the interfaces. That is, to define * the hierarchy at the level of interfaces because interfaces are * not meant to be replaced and then the OCP principle would not apply * but we still would be respecting the ISP principle. In this solution * the clients (CarWorkshop and BikeWorkshop) depend on the respective * interface they need. */ public interface IVehicleMaintenance { public void void cleanExterior(); // Other operations that are common to all vehicles } public interface ICarMaintenance extends IVehicleMaintenace { public void changeWheel(int number); public void changeEngineOil(); public void checkBattery(); // Other operations that are common to all cars } public interface IBikeMaintenance extends IVehicleMaintenace { public void lubeChain(); // Other operations that are common to all bikes } public class CarWorkshop implements ICarMaintenance { public void cleanExterior() { // ... } public void changeWheel(int number) { // ... } public void changeEngineOil() { // ... } public void checkBattery() { // ... } // Other operations that are common to all cars } public class BikeWorkshop implements IBikeMaintenance { public void cleanExterior() { // ... } public void lubeChain() { // ... } // Other operations that are common to all bikes } //////////////////////////////////////////////////////////////////// /* * A good solution comes from rethinking our abstractions and using * a more generic the interface that relies on the parameters of the * operations to handle the differences. In such case, we would have * ended up with a simpler design that would have respected both the OCP * and the ISP principles. In some cases this is enough and can work */ public interface IMaintainable { public void void maintain(); } public abstract class MaintenanceOperation { // The only thing we want to ensure is that all operations can be executed abstract void execute(); } /* * We can now extend the MaintenanceOperation class to reflect the different * maintenance operations that each vehicle type can accept */ public enum CarMaintenanceTypes { CLEANEXTERIOR, CHANGEWHEEL, CHANGEENGINEOIL, CHECKBATTERY } } public enum BikeMaintenanceTypes { CLEANEXTERIOR, LUBECHAIN } public class CarMaintenanceOperation extends MaintenanceOperation { private CarMaintenanceTypes opType; private CarMaintenanceParameters opParams; public CarMaintenanceOperation (CarMaintenanceTypes opType, CarMaintenanceParameters opParams){ this.opType = opType; this.opParams = opParams; } public void execute(){ // specific implementation } } public class BikeMaintenanceOperation extends MaintenanceOperation { private BikeMaintenanceTypes opType; private BikeMaintenanceParameters opParams; public CarMaintenanceOperation (BikeMaintenanceTypes opType, BikeMaintenanceParameters opParams){ this.opType = opType; this.opParams = opParams; } public void execute(){ // specific implementation } } public class Vehicle implements IMaintainable { // Vehicle data and operations Collection<MaintenanceOperation> pendingOperations = new ArrayList<MaintenanceOperation>(); public void void maintain(){ Iterator itr=pendingOperations.iterator(); while(itr.hasNext()){ itr.next().execute(); } } } /* * The only thing we have to do now it to make sure each vehicle is * only assigned to maintenance operations that are applicable to it */ public class Car extends Vehicle { public void addPendingMaintenanceOperation(CarMaintenanceOperation op); // ... } public class Bike extends Vehicle { public void addPendingMaintenanceOperation(BikeMaintenanceOperation op); // ... } //////////////////////////////////////////////////////////////////// |
Open-closed Principle
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 |
public class AccountClient { public boolean doSomething(Account a) { switch (a.account_type) { case checkingAcc : foo(a); break; case savingsAcc : bar(a); break; case investmentAcc : qux(a); break; default : hmmm(a); } /* * Problem: it is impossible to add a new account type without modifying the code. */ } |
Singleton
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 |
/* * Normally, we adopt a "lazy instantiation" strategy, in which we * do not create the instance until it is required for the first * time. */ class Singular { private static Singular theInstance; ... // attribute declaration /* * Here, we declare the constructor as private so that it can not be * invoked from outside the class (that is, we prevent client classes * creating instances by using "new"). */ private Singular(...) { ... // attribute initialization } /* * The "getSingularInstance" is a factory method that is in charge of * returning the instance, and also of creating it in the first place. */ public static Singular getSingularInstance() { if (theInstance == null) theInstance = new Singular(...); return theInstance; } // instance methods } /* * If we use multi-threading and we adopt a "lazy instantiation" strategy, * we may have situations in which two calls to "getSingularInstance" * arrive at the same time resulting in two instances being created. * To avoid this, we must use sinchronization in the call to this method. */ class Singular { private static Singular theInstance; ... // attribute declaration private Singular(...) { ... // attribute initialization } /* * The "getSingularInstance" is now declared as synchronized, which * prevents problems with multi-threading. */ public static synchronized Singular getSingularInstance() { if (theInstance == null) theInstance = new Singular(...); return theInstance; } // instance methods } /* * If the performance is important, which is seldom the case, the use * of synchronization (which is slow) may be limited to using it only * for the creation of theInstance and not in subsequent calls to the * method. To do this, we must declare the theInstance variable as * volatile, in order to inform the compiler that it must be always be * read from the main memory (avoiding caching). */ private static volatile Singular theInstance; /* * ...and we must also we must modify the getSingularInstance method as * shown in the following code. Please note that it is only when we need * to create the instance we enter the synchronized section, and in that * case we must check again inside that section. For this reason, this * approach is called double-check locking. */ public static Singular getSingularInstance() { if (theInstance == null) { synchronized(Singular.class) { if (theInstance == null) { theInstance = new Singular(...); } } } return theInstance; } /* * Alternatively, we may adopt an "early instantiation" strategy, in which * we create the instance at the time of classloading. */ class Singular { private static Singular theInstance = new Singular(...); ... // attribute declaration private Singular(...) { ... // attribute initialization } /* * The "getSingularInstance" is simpler in this case since we do not * need to check the existence of the instance. */ public static Singular getSingularInstance() { return theInstance; } // instance methods } |
Single Responsibility Principle
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 |
/* + The following class has two reasons to change: one is that + the product definition/behaviour changes; the other is that + the tax calculation (values or formula) changes */ class Product { public title : string; public price : double; public taxRate : double; constructor(title, price, taxRate) { this.title = title; this.price = price; this.taxRate = taxRate; } calculateTax() { return this.price * this.taxRate; } } /* * A solution is to split the class in two, each one with just one * responsibility (reason to change). In this solution the tax rate * remains in the Product class, because it is different for * different products */ class Product { public title : string; private price : double; private taxRate : double; constructor(title, price, taxRate) { this.title = title; this.price = price; this.taxRate = taxRate; } getPrice() { return this.price; } getTaxRate() { return this.taxRate; } } class TaxCalculator { static calculateTax(product) { return product.getPrice() * product.getTaxRate(); } } /* * Still the product has the tax rate "hardcoded" in it. * A better solution is to identify the real "owner" of the * tax rate in the domain model, which is the ProductCategory, * because in real world the tax rates depend on the category * of the products. */ class Product { public title : string; private price : double; private category : ProductCategory; constructor(title, price, category) { this.title = title; this.price = price; this.category = category; } getPrice() { return this.price; } getCategory() { return this.category; } } class ProductCategory { public title : string; public taxRate : double; constructor(title, taxRate) { this.title = title; this.taxRate = taxRate; } getTaxRate() { return this.taxRate; } } class TaxCalculator { static calculateTax(product) { return product.getPrice() * product.getCategory().getTaxRate(); } } |