See Objects As Bundles Of Behavior, Not Bundles Of Data

  • June 2020
  • PDF

This document was uploaded by user and they confirmed that they have the permission to share it. If you are author or own the copyright of this book, please report to us by using this DMCA report form. Report DMCA


Overview

Download & View See Objects As Bundles Of Behavior, Not Bundles Of Data as PDF for free.

More details

  • Words: 3,678
  • Pages: 12
See objects as bundles of behavior, not bundles of data Interface Design by Bill Venners you should think of objects primarily as bundles of behavior, not bundles of data. Why should you think of objects as bundles of services? If data is exposed, code that manipulates that data gets spread across the program. If higher-level services are exposed, code that manipulates the data is concentrated in one place: the class. This concentration reduces code duplication, localizes bug fixes, and makes it easier to achieve robustness. A Data-Oriented Matrix Consider the Matrix class shown in Listing 2-1, whose instances act more like bundles of data than bundles of behavior. Although the instance variables declared in this class are private, the only services it offers besides equals, hashcode, and clone are accessor methods set, get, getCols, and getRows. These accessor methods are very data oriented, because they don't do anything interesting with the object's state. They just provide clients with access to the state. Listing 2-1. A data-oriented matrix. 1 package com.artima.examples.matrix.ex1; 2 3 /** 4 * Represents a matrix each of whose elements is an int. 5 */ 6 public class Matrix { 7 8 private int[][] elements; 9 private int rowCount; 10 private int colCount; 11 12 /** 13 * Construct a new <EM>square zero matrix whose order is determined 14 * by the passed number of rows. (The matrix is square. It has the 15 * same number of rows and columns.) 16 * All elements of the new Matrix 17 * will be initialized to zero. 18 */ 19 public Matrix(int rows) { 20 elements = new int[rows][rows]; 21 rowCount = rows; 22 colCount = rows; 23 } 24 25 /** 26 * Construct a new <EM>zero matrix whose order is determined 27 * by the passed number of rows and columns. The order is (rows by columns). 28 * All elements of the new Matrix

29 * will be initialized to zero. 30 * 31 * @param rows The number of rows in the new Matrix 32 * @param cols The number of columns in the new Matrix 33 * @exception IllegalArgumentException if rows or cols is less than zero 34 */ 35 public Matrix(int rows, int cols) { 36 if (rows < 0 || cols < 0) { 37 throw new IllegalArgumentException(); 38 } 39 elements = new int[rows][cols]; 40 rowCount = rows; 41 colCount = cols; 42 } 43 44 /** 45 * Construct a new Matrix whose elements will be initialized 46 * with values from the passed two-dimensional array of ints. 47 * The order of the matrix will be determined by the sizes of the passed arrays. 48 * For example, a two dimensional array constructed with new int[4][9], 49 * would yield a matrix whose order is 4 by 9. The lengths of each of the arrays 50 * held from the initial array must be the same. The twodimensional array passed 51 * as init will not be used as part of the state of the newly constructed 52 * Matrix object. 53 */ 54 public Matrix(int[][] init) { 55 56 checkValidity(init); 57 58 elements = (int[][]) init.clone(); 59 rowCount = init.length; 60 colCount = init[0].length; 61 } 62 63 /** 64 * Returns the element value at the specified row and column. 65 */ 66 public int get(int row, int col) { 67 checkIndices(row, col); 68 return elements[row][col]; 69 } 70 71 /** 72 * Sets the element value at the specified row and column to the 73 * passed value. 74 */ 75 public void set(int row, int col, int value) { 76 checkIndices(row, col);

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 0) { 126 127 128 129 }

}

elements[row][col] = value;

/** * Returns the number of rows in this matrix. */ public int getRows() { return rowCount; } /** * Returns the number of cols in this matrix. */ public int getCols() { return colCount; } /** * Ensures passed two-dimensional array is valid * for initializing a Matrix object. */ private static void checkValidity(int[][] val) {

}

try { int rows = val.length; if (rows == 0) { throw new IllegalArgumentException(); } int cols = val[0].length; if (cols == 0) { throw new IllegalArgumentException(); } for (int i = 1; i < rows; ++i) { if (val[i].length != cols) { throw new IllegalArgumentException(); } } } catch (NullPointerException e) { throw new IllegalArgumentException(); }

/** * Ensures passed row and column represent valid indices into * this Matrix. */ private void checkIndices(int row, int col) { if (row >= rowCount || row < 0 || col >= colCount || col < } }

throw new IndexOutOfBoundsException();

Listing 2-2 shows an example of a client of the data-oriented Matrix. This client wants to add two matrices and print the sum to the standard output: Listing 2-2. A client of the data-oriented matrix. 1 package com.artima.examples.matrix.ex1; 2 3 class Example1 { 4 5 public static void main(String[] args) { 6 7 int[][] init1 = { {2, 2}, {2, 2} }; 8 int[][] init2 = { {1, 2}, {3, 4} }; 9 10 Matrix m1 = new Matrix(init1); 11 Matrix m2 = new Matrix(init2); 12 13 // Add m1 & m2, store result in a new Matrix object 14 Matrix sum = new Matrix(2, 2); 15 for (int i = 0; i < 2; ++i) { 16 for (int j = 0; j < 2; ++j) { 17 int addend1 = m1.get(i, j); 18 int addend2 = m2.get(i, j); 19 sum.set(i, j, addend1 + addend2); 20 } 21 } 22 23 // Print out the sum 24 System.out.print("Sum: {"); 25 for (int i = 0; i < 2; ++i) { 26 for (int j = 0; j < 2; ++j) { 27 int val = sum.get(i, j); 28 System.out.print(val); 29 if (i == 0 || j == 0) { 30 System.out.print(", "); 31 } 32 } 33 } 34 System.out.println("}"); 35 36 } 37 }

This all works fine, but imagine if there were 50 different places in your system where you needed to add two matrices. The code shown from lines 14 to 21 of Example1 would have to be replicated in 50 different places. Perhaps in 46 of those places would perform flawless matrix addition, but in four of those places a bug existed. If you detected and fixed a bug in one of those four buggy places, you'd still have three matrix addition bugs lurking elsewhere. A similar problem would exist in printing out the value of the matrix. If there were 50 places where you wanted to print out a matrix, the code shown in lines 24 to 33 of Example1 would appear in 50 different places, with the same maintanence problems.

A Service-Oriented Matrix By contrast, consider the Matrix class shown in Listing 2-3, which you can think of as a second iteration in the design of this class. In this iteration, Matrix retains the get methods that return information about the object's state, but the set method of the previous iteration has been replaced by more service-oriented methods: add, subtract, and multiply. In addition, a toString method has been added, which produces a String representing the state of the Matrix. /** 121 * Adds the passed Matrix to this one. 122 * The order of the passed Matrix must be identical 123 * to the order of this Matrix. 124 * 125 *

126 * The sum of two Matrix objects is a Matrix 127 * of the same order of the two addends. Each element of the sum 128 * Matrix is equal to the sum of the corresponding elements 129 * in the Matrix addends. For example: 130 * 131 *

 132 * | 1 2 3 | | 9 -8 7 | | 10 -6 10 | 133 * | 4 5 6 | + | -6 5 -4 | = | -2 10 2 | 134 * | 7 8 9 | | -3 2 -1 | | 4 10 8 | 135 * 
136 * 137 *

138 * This method does not throw any exception on overflow. 139 * 140 * @param addend the Matrix to add to this one 141 * @return The sum of this Matrix and the passed Matrix 142 * @exception IllegalArgumentException if the order of the passed 143 * Matrix object differs from the order of this Matrix 144 */ 145 public Matrix add(Matrix addend) { 146 147 // Make sure addend has the same order as this matrix 148 if ((addend.rowCount != rowCount) || (addend.colCount != colCount)) { 149 throw new IllegalArgumentException(); 150 } 151 152 Matrix retVal = new Matrix(elements); 153 for (int row = 0; row < rowCount; ++row) { 154 for (int col = 0; col < colCount; ++col) { 155 retVal.elements[row][col] += addend.elements[row][col]; 156 } 157 } 158 return retVal; 159 }

160 161 /** 162 * Subtracts the passed Matrix from this one. 163 * The order of the passed Matrix must be identical 164 * to the order of this Matrix. Returned Matrix 165 * equals the sum of this Matrix and the negation of the 166 * passed Matrix. 167 * 168 *

169 * The difference of two Matrix objects is a Matrix 170 * of the same order of the minuend and subtrahend. Each element of the sum 171 * Matrix is equal to the difference of the corresponding elements 172 * in the minuend (this) and subtrahend (passed) Matrix objects. 173 * For example: 174 * 175 *

 176 * | 1 2 3 | | 9 -8 7 | | -8 10 -4 | 177 * | 4 5 6 | - | -6 5 -4 | = | 10 0 10 | 178 * | 7 8 9 | | -3 2 -1 | | 10 6 10 | 179 * 
180 * 181 *

182 * This method does not throw any exception on overflow. 183 * 184 * @param subtrahend the Matrix to subtract from this one 185 * @return The difference of this Matrix and the passed Matrix 186 * @exception IllegalArgumentException if the order of the passed 187 * Matrix object differs from the order of this Matrix 188 */ 189 public Matrix sub(Matrix subtrahend) { 190 191 // To be subtracted, subtrahend must have the same order 192 if ((subtrahend.rowCount != rowCount) || (subtrahend.colCount != colCount)) { 193 throw new IllegalArgumentException(); 194 } 195 196 Matrix retVal = new Matrix(elements); 197 for (int row = 0; row < rowCount; ++row) { 198 for (int col = 0; col < colCount; ++col) { 199 retVal.elements[row][col] -= subtrahend.elements[row][col]; 200 } 201 } 202 return retVal; 203 } 204 205 /**

206 * Multiplies this matrix by the passed scalar. Returns 207 * a new matrix representing the result of the multiplication. 208 * To negate a matrix, for example, just multiply it by 209 * -1. 210 * 211 *

212 * The product of a Matrix and a scalar is a Matrix 213 * of the same order as the Matrix multiplicand. Each element of the product 214 * Matrix is equal to the product of the corresponding element 215 * in the Matrix multiplicand and the scalar multiplier. For example: 216 * 217 *

 218 * | 1 2 3 | | -2 -4 -6 | 219 * -2 * | 4 5 6 | = | -8 -10 -12 | 220 * | 7 8 9 | | -14 -16 -18 | 221 * 
222 * 223 *

224 * This method does not throw any exception on overflow. 225 * 226 * @param addend the Matrix to add to this one 227 * @return The sum of this Matrix and the passed Matrix 228 * @exception IllegalArgumentException if the order of the passed 229 * Matrix object differs from the order of this Matrix 230 */ 231 public Matrix mult(int scalar) { 232 233 Matrix retVal = new Matrix(elements); 234 for (int row = 0; row < rowCount; ++row) { 235 for (int col = 0; col < colCount; ++col) { 236 retVal.elements[row][col] *= scalar; 237 } 238 } 239 return retVal; 240 } 241 242 /** 243 * Multiplies this Matrix (the multiplicand) by the passed 244 * Matrix (the multiplier). The number of columns in this 245 * multiplicand Matrix must equal the number rows in the 246 * passed multiplier Matrix. 247 * 248 *

249 * The product of two Matrix objects is a Matrix that has 250 * the same number of rows as the multiplicand (this Matrix) and the

251 * same number of columns as the multiplier (passed Matrix). 252 * Each element of the product Matrix is equal to sum of the products 253 * of the elements of corresponding multiplicand row and multiplier column. 254 * For example: 255 * 256 *

 257 * | 0 1 | | 6 7 | | (0*6 + 1*8) (0*7 + 1*9) | | 8 9 | 258 * | 2 3 | * | 8 9 | = | (2*6 + 3*8) (2*7 + 3*9) | = | 36 41 | 259 * | 4 5 | | (4*6 + 5*8) (4*7 + 5*9) | | 64 73 | 260 * 
261 * 262 *

263 * This method does not throw any exception on overflow. 264 * 265 * @param multiplier the Matrix to multiply to this one 266 * @return A new Matrix representing the product of this 267 * Matrix and the passed Matrix 268 * @exception IllegalArgumentException if the number of rows of the passed 269 * Matrix object differs from the number of columns of 270 * this Matrix 271 */ 272 public Matrix mult(Matrix multiplier) { 273 274 // To do a matrix multiplication, the number of columns in this 275 // matrix must equal the number of rows of the passed multiplicand. 276 if (colCount != multiplier.rowCount) { 277 throw new IllegalArgumentException(); 278 } 279 280 // Calculate order of result 281 int resultRows = rowCount; 282 int resultCols = multiplier.colCount; 283 284 // Create array for result 285 int[][] resultArray = new int[resultRows][resultCols]; 286 287 Matrix retVal = new Matrix(elements); 288 for (int row = 0; row < resultRows; ++row) { 289 for (int col = 0; col < resultCols; ++col) { 290 for (int i = 0; i < colCount; ++i) { 291 resultArray[row][col] += elements[row][i] * multiplier.elements[i][col]; 292 } 293 } 294 } 295 return retVal; 296 } 297

298 299 300 301 302 303 304 305 306 307 308 309 310 311 312 313 314 315 316 317 318 of 319 320 321 322 323 324 325 326 327 328 329 330 331 332 333 334 335 336 337 338 339 340 341 342 343 344 345 346 347 348 349 350 351 352 353

/** * Returns a String that contains the * integer values of the elements of this * Matrix. Each row of element values * is enclosed in parentheses and separated by * commas, and the entire result is enclosed in * a set of parentheses. For example, for the matrix: * *

 * | 1 2 3 | * | 4 5 6 | * | 7 8 9 | * 
* * This method would return the string: * *
 * ((1, 2, 3), (4, 5, 6), (7, 8, 9)) * 
* * @return A new String representation of the state * this Matrix */ public String toString() { StringBuffer retVal = new StringBuffer("(");

}

for (int row = 0; row < rowCount; ++row) { retVal.append("("); for (int col = 0; col < colCount; ++col) { retVal.append(elements[row][col]); if (col != colCount - 1) { retVal.append(", "); } } retVal.append(")"); if (row != rowCount - 1) { retVal.append(", "); } } retVal.append(")"); return retVal.toString();

/** * Clones this object. * * @return A clone of this Matrix */ public Object clone() { try { Matrix clone = (Matrix) super.clone(); clone.elements = new int[rowCount][colCount]; for (int row = 0; row < rowCount; ++row) { for (int col = 0; col < colCount; ++col) {

354 clone.elements[row][col] = elements[row][col]; 355 } 356 } 357 return clone; 358 } 359 catch (CloneNotSupportedException e) { 360 // Can't happen 361 throw new InternalError(); 362 } 363 } 364 365 /** 366 * Compares passed Matrix to this 367 * Matrix for equality. Two Matrix 368 * objects are semantically equal if they have the same 369 * order (i.e., same number of rows and columns), and 370 * the int value of each element in 371 * this Matrix is equal to the corresponding 372 * int value in the passed Matrix. 373 * 374 * @param An object to compare to this Matrix 375 * @return true if this Matrix is semantically equal 376 * to the passed Matrix 377 */ 378 public boolean equals(Object o) { 379 380 if ((o == null) || (getClass() != o.getClass())) { 381 return false; 382 } 383 384 Matrix m = (Matrix) o; 385 386 // Because this class extends Object, don't 387 // call super.equals() 388 389 // To be semantically equal, both matrices must 390 // have the same order 391 if ((rowCount != m.rowCount) || (colCount != m.colCount)) { 392 return false; 393 } 394 395 // To be semantically equal, corresponding 396 // elements of both matrices must be equal 397 for (int row = 0; row < rowCount; ++row) { 398 for (int col = 0; col < colCount; ++col) { 399 400 if (elements[row][col] != m.elements[row][col]) { 401 return false; 402 } 403 } 404 } 405 406 return true; 407 } 408 409 /**

410 411 412 413 414 415 416 417 418 419 420 421 422 423 424 425 426 427 428 429 430 431 432 433 434 435 436 437 438 439 440 441 442 443 444 445 446 447 448 449 450 451 452 453 454 455 456 457 458 459 0) { 460 461 462 463 }

* Computes the hash code for this Matrix. * * @return a hashcode value for this Matrix */ public int hashcode() { int retVal = rowCount * colCount; for (int row = 0; row < rowCount; ++row) { for (int col = 0; col < colCount; ++col) { retVal *= elements[row][col]; }

}

return retVal; } /** * Ensures passed two-dimensional array is valid * for initializing a Matrix object. */ private static void checkValidity(int[][] val) { try { int rows = val.length; if (rows == 0) { throw new IllegalArgumentException(); } int cols = val[0].length; if (cols == 0) { throw new IllegalArgumentException(); } for (int i = 1; i < rows; ++i) { if (val[i].length != cols) { throw new IllegalArgumentException(); } } } catch (NullPointerException e) { throw new IllegalArgumentException(); } } /** * Ensures passed row and column represent valid indices into * this Matrix. */ private void checkIndices(int row, int col) { if (row >= rowCount || row < 0 || col >= colCount || col < throw new IndexOutOfBoundsException(); }

}

The data required for matrix addition sits inside instances of class Matrix, in the elements, rowCount, and colCount instance variables. In this second iteration of class Matrix, the code that performs matrix addition has been moved to the class that contains the data. In the previous iteration, this code existed outside class Matrix, in the Example1 client shown in Listing 2-2. This code now shows up in the Matrix class's add method, lines 145 to 159 of Listing 2-3. Similarly, the code for building a String representation of the Matrix has also been moved to the data. This code shows up in the toString method, lines 321 to 340. In the previous iteration this code existed outside class Matrix, in lines 24 to 34 of Example1. These changes allow clients, rather than performing the add and string building services themselves, to ask the Matrix object to perform those services for them. Clients can now delegate responsibility for matrix addition and string building to the class that has the necessary data, class Matrix. For example, consider the Example2 client shown in Listing 2-4. Example2 performs the same function as Example1, it adds two matrices and prints the result. But Example2 is a client of the new improved Matrix of Listing 2-3: Listing 2-4. A client of the service-oriented matrix. 1 package com.artima.examples.matrix.ex2; 2 3 class Example2 { 4 5 public static void main(String[] args) { 6 7 int[][] init1 = { {2, 2}, {2, 2} }; 8 int[][] init2 = { {1, 2}, {3, 4} }; 9 10 Matrix m1 = new Matrix(init1); 11 Matrix m2 = new Matrix(init2); 12 13 // Add m1 & m2, store result in a new matrix object 14 Matrix sum = m1.add(m2); 15 16 // Print out the sum 17 System.out.println("Sum: " + sum.toString()); 18 } 19 }

Now, instead of Example1's 8 lines of code (lines 14 to 21) that performs matrix addition, Example2 needs just one line of code, line 14. Similarly, instead of Example1's 10 lines of code (lines 24 to 33) to print out a matrix, Example2 requires 1 line of code, line 17. If matrix addition needs to occur 50 different places, only the one-liner shown on line 14 needs to be replicated throughout the system. If a bug is detected in the addition algorithm, only one place need be debugged and fixed, the add method in class Matrix. Once the bug is fixed, you know you've fixed it everywhere matrix addition is performed.

Related Documents