Sponsored Link •
|
Advertisement
|
One of the most basic object-oriented ideas is encapsulation -- associating data with code that manipulates the data. The data, stored in instance variables, represents the object's state. The code, stored in instance methods, represents the object's behavior. Because of encapsulation, therefore, you can think of objects as either bundles of data, bundles of behavior, or both. To reap the greatest benefit from encapsulation, however, you should think of objects primarily as bundles of behavior, not bundles of data. You should think of objects less as carriers of information, embodied in the data, and more as providers of services, represented by the behavior.
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.
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 <CODE>int</CODE>. 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</EM> 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 <CODE>Matrix</CODE> 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</EM> 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 <CODE>Matrix</CODE> 29 * will be initialized to zero. 30 * 31 * @param rows The number of rows in the new <CODE>Matrix</CODE> 32 * @param cols The number of columns in the new <CODE>Matrix</CODE> 33 * @exception IllegalArgumentException if <code>rows</code> or <code>cols</code> 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 <CODE>Matrix</CODE> whose elements will be initialized 46 * with values from the passed two-dimensional array of <CODE>int</CODE>s. 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 <CODE>new int[4][9]</CODE>, 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 two-dimensional array passed 51 * as <CODE>init</CODE> will not be used as part of the state of the newly constructed 52 * <CODE>Matrix</CODE> 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 <CODE>value</CODE>. 74 */ 75 public void set(int row, int col, int value) { 76 checkIndices(row, col); 77 elements[row][col] = value; 78 } 79 80 /** 81 * Returns the number of rows in this matrix. 82 */ 83 public int getRows() { 84 return rowCount; 85 } 86 87 /** 88 * Returns the number of cols in this matrix. 89 */ 90 public int getCols() { 91 return colCount; 92 } 93 94 /** 95 * Ensures passed two-dimensional array is valid 96 * for initializing a <CODE>Matrix</CODE> object. 97 */ 98 private static void checkValidity(int[][] val) { 99 100 try { 101 int rows = val.length; 102 if (rows == 0) { 103 throw new IllegalArgumentException(); 104 } 105 int cols = val[0].length; 106 if (cols == 0) { 107 throw new IllegalArgumentException(); 108 } 109 for (int i = 1; i < rows; ++i) { 110 if (val[i].length != cols) { 111 throw new IllegalArgumentException(); 112 } 113 } 114 } 115 catch (NullPointerException e) { 116 throw new IllegalArgumentException(); 117 } 118 } 119 120 /** 121 * Ensures passed row and column represent valid indices into 122 * this <CODE>Matrix</CODE>. 123 */ 124 private void checkIndices(int row, int col) { 125 if (row >= rowCount || row < 0 || col >= colCount || col < 0) { 126 throw new IndexOutOfBoundsException(); 127 } 128 } 129 }
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 }
To add the matrices, Example1
in Listing 2-2 first instantiates a matrix to hold
the sum. Then, for each row and column, Example1
retrieves an element
value from each addend matrix using get
. Example1
adds the two values
and stuffs the result into the corresponding row and column of the sum matrix using set
.
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.
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
.
Listing 2-3. A service-oriented matrix.
1 package com.artima.examples.matrix.ex2; 2 3 import java.io.Serializable; 4 5 /** 6 * A two-dimensional matrix of <CODE>int</CODE>s. 7 * 8 * <P> 9 * The <em>order</em> of 10 * the matrix is its number of rows and columns. For example, the order 11 * of a matrix with 5 rows and 4 columns is "5 by 4." A matrix with the 12 * same number of rows and columns, such as a 3 by 3 matrix, is a 13 * <em>square matrix</em>. A matrix all of whose elements is zero is 14 * a <em>zero matrix</em>. 15 * 16 * <P> 17 * Instances of <CODE>Matrix</CODE> are immutable. 18 */ 19 public class Matrix implements Serializable, Cloneable { 20 21 private int[][] elements; 22 private int rowCount; 23 private int colCount; 24 25 /** 26 * Construct a new square <code>Matrix</code> whose order is determined 27 * by the passed number of rows. 28 * Yields a zero matrix, i.e., all elements of the new <CODE>Matrix</CODE> 29 * will be initialized to zero. 30 * 31 * @param rows The number of rows and cols in the new square <CODE>Matrix</CODE> 32 * @exception IllegalArgumentException if <code>rows</code> or <code>cols</code> is less than 1 33 */ 34 public Matrix(int rows) { 35 if (rows < 1) { 36 throw new IllegalArgumentException(); 37 } 38 elements = new int[rows][rows]; 39 rowCount = rows; 40 colCount = rows; 41 } 42 43 /** 44 * Construct a new <EM>zero matrix</EM> whose order is determined 45 * by the passed number of rows and columns. The order is (rows by columns). 46 * Yields a zero matrix, i.e., all elements of the new <CODE>Matrix</CODE> 47 * will be initialized to zero. 48 * 49 * @param rows The number of rows in the new <CODE>Matrix</CODE> 50 * @param cols The number of columns in the new <CODE>Matrix</CODE> 51 * @exception IllegalArgumentException if <code>rows</code> or <code>cols</code> is less than 1 52 */ 53 public Matrix(int rows, int cols) { 54 if (rows < 1 || cols < 1) { 55 throw new IllegalArgumentException(); 56 } 57 elements = new int[rows][cols]; 58 rowCount = rows; 59 colCount = cols; 60 } 61 62 /** 63 * Construct a new <CODE>Matrix</CODE> whose elements will be initialized 64 * with values from the passed two-dimensional array of <CODE>int</CODE>s. 65 * The order of the matrix will be determined by the sizes of the passed arrays. 66 * For example, a two dimensional array constructed with <CODE>new int[4][9]</CODE>, 67 * would yield a matrix whose order is 4 by 9. The lengths of each of the arrays 68 * held from the initial array must be the same. The two-dimensional array passed 69 * as <CODE>init</CODE> will not be used as part of the state of the newly constructed 70 * <CODE>Matrix</CODE> object. 71 * 72 * @param rows The number of rows in the new <CODE>Matrix</CODE> 73 * @param cols The number of columns in the new <CODE>Matrix</CODE> 74 * @exception IllegalArgumentException if the length of any passed array is zero, 75 * or if the length of all the secondary arrays are not equivalent. 76 */ 77 public Matrix(int[][] init) { 78 79 checkValidity(init); 80 81 elements = (int[][]) init.clone(); 82 rowCount = init.length; 83 colCount = init[0].length; 84 } 85 86 /** 87 * Returns the element value at the specified row and column. 88 * 89 * @param row The row of the element whose value is to be returned 90 * @param col The column of the element whose value is to be returned 91 * @return value of element at specified row and column 92 * @exception IndexOutOfBoundsException if <code>row</code> is 93 * less than zero or greater than the number of rows minus 1, or if 94 * <code>col</code> is less than 0 or greater than the number of 95 * columns minus 1. 96 */ 97 public int get(int row, int col) { 98 checkIndices(row, col); 99 return elements[row][col]; 100 } 101 102 /** 103 * Returns the number of rows in this <code>Matrix</code>. 104 * 105 * @return number of rows in this <code>Matrix</code> 106 */ 107 public int getRows() { 108 return rowCount; 109 } 110 111 /** 112 * Returns the number of columns in this <code>Matrix</code>. 113 * 114 * @return number of columns in this <code>Matrix</code> 115 */ 116 public int getCols() { 117 return colCount; 118 } 119 120 /** 121 * Adds the passed <code>Matrix</code> to this one. 122 * The order of the passed <code>Matrix</code> must be identical 123 * to the order of this <code>Matrix</code>. 124 * 125 * <P> 126 * The sum of two <code>Matrix</code> objects is a <code>Matrix</code> 127 * of the same order of the two addends. Each element of the sum 128 * <code>Matrix</code> is equal to the sum of the corresponding elements 129 * in the <code>Matrix</code> addends. For example: 130 * 131 * <PRE> 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 * </PRE> 136 * 137 * <P> 138 * This method does not throw any exception on overflow. 139 * 140 * @param addend the <code>Matrix</code> to add to this one 141 * @return The sum of this <code>Matrix</code> and the passed <code>Matrix</code> 142 * @exception IllegalArgumentException if the order of the passed 143 * <code>Matrix</code> object differs from the order of this <code>Matrix</code> 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 <code>Matrix</code> from this one. 163 * The order of the passed <code>Matrix</code> must be identical 164 * to the order of this <code>Matrix</code>. Returned <code>Matrix</code> 165 * equals the sum of this <code>Matrix</code> and the negation of the 166 * passed <code>Matrix</code>. 167 * 168 * <P> 169 * The difference of two <code>Matrix</code> objects is a <code>Matrix</code> 170 * of the same order of the minuend and subtrahend. Each element of the sum 171 * <code>Matrix</code> is equal to the difference of the corresponding elements 172 * in the minuend (this) and subtrahend (passed) <code>Matrix</code> objects. 173 * For example: 174 * 175 * <PRE> 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 * </PRE> 180 * 181 * <P> 182 * This method does not throw any exception on overflow. 183 * 184 * @param subtrahend the <code>Matrix</code> to subtract from this one 185 * @return The difference of this <code>Matrix</code> and the passed <code>Matrix</code> 186 * @exception IllegalArgumentException if the order of the passed 187 * <code>Matrix</code> object differs from the order of this <code>Matrix</code> 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 * <P> 212 * The product of a <code>Matrix</code> and a scalar is a <code>Matrix</code> 213 * of the same order as the <code>Matrix</code> multiplicand. Each element of the product 214 * <code>Matrix</code> is equal to the product of the corresponding element 215 * in the <code>Matrix</code> multiplicand and the scalar multiplier. For example: 216 * 217 * <PRE> 218 * | 1 2 3 | | -2 -4 -6 | 219 * -2 * | 4 5 6 | = | -8 -10 -12 | 220 * | 7 8 9 | | -14 -16 -18 | 221 * </PRE> 222 * 223 * <P> 224 * This method does not throw any exception on overflow. 225 * 226 * @param addend the <code>Matrix</code> to add to this one 227 * @return The sum of this <code>Matrix</code> and the passed <code>Matrix</code> 228 * @exception IllegalArgumentException if the order of the passed 229 * <code>Matrix</code> object differs from the order of this <code>Matrix</code> 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 <code>Matrix</code> (the multiplicand) by the passed 244 * <code>Matrix</code> (the multiplier). The number of columns in this 245 * multiplicand <code>Matrix</code> must equal the number rows in the 246 * passed multiplier <code>Matrix</code>. 247 * 248 * <P> 249 * The product of two <code>Matrix</code> objects is a <code>Matrix</code> that has 250 * the same number of rows as the multiplicand (this <code>Matrix</code>) and the 251 * same number of columns as the multiplier (passed <code>Matrix</code>). 252 * Each element of the product <code>Matrix</code> is equal to sum of the products 253 * of the elements of corresponding multiplicand row and multiplier column. 254 * For example: 255 * 256 * <PRE> 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 * </PRE> 261 * 262 * <P> 263 * This method does not throw any exception on overflow. 264 * 265 * @param multiplier the <code>Matrix</code> to multiply to this one 266 * @return A new <code>Matrix</code> representing the product of this 267 * <code>Matrix</code> and the passed <code>Matrix</code> 268 * @exception IllegalArgumentException if the number of rows of the passed 269 * <code>Matrix</code> object differs from the number of columns of 270 * this <code>Matrix</code> 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 * Returns a <code>String</code> that contains the 300 * integer values of the elements of this 301 * <code>Matrix</code>. Each row of element values 302 * is enclosed in parentheses and separated by 303 * commas, and the entire result is enclosed in 304 * a set of parentheses. For example, for the matrix: 305 * 306 * <PRE> 307 * | 1 2 3 | 308 * | 4 5 6 | 309 * | 7 8 9 | 310 * </PRE> 311 * 312 * This method would return the string: 313 * 314 * <PRE> 315 * ((1, 2, 3), (4, 5, 6), (7, 8, 9)) 316 * </PRE> 317 * 318 * @return A new <code>String</code> representation of the state of 319 * this <code>Matrix</code> 320 */ 321 public String toString() { 322 323 StringBuffer retVal = new StringBuffer("("); 324 325 for (int row = 0; row < rowCount; ++row) { 326 retVal.append("("); 327 for (int col = 0; col < colCount; ++col) { 328 retVal.append(elements[row][col]); 329 if (col != colCount - 1) { 330 retVal.append(", "); 331 } 332 } 333 retVal.append(")"); 334 if (row != rowCount - 1) { 335 retVal.append(", "); 336 } 337 } 338 retVal.append(")"); 339 return retVal.toString(); 340 } 341 342 /** 343 * Clones this object. 344 * 345 * @return A clone of this <code>Matrix</code> 346 */ 347 public Object clone() { 348 try { 349 Matrix clone = (Matrix) super.clone(); 350 clone.elements = new int[rowCount][colCount]; 351 352 for (int row = 0; row < rowCount; ++row) { 353 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 <CODE>Matrix</CODE> to this 367 * <code>Matrix</code> for equality. Two <code>Matrix</code> 368 * objects are semantically equal if they have the same 369 * order (i.e., same number of rows and columns), and 370 * the <code>int</code> value of each element in 371 * this <code>Matrix</code> is equal to the corresponding 372 * <code>int</code> value in the passed <code>Matrix</code>. 373 * 374 * @param An object to compare to this <code>Matrix</code> 375 * @return <code>true</code> if this <code>Matrix</code> is semantically equal 376 * to the passed <code>Matrix</code> 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 * Computes the hash code for this <code>Matrix</code>. 411 * 412 * @return a hashcode value for this <code>Matrix</code> 413 */ 414 public int hashcode() { 415 416 int retVal = rowCount * colCount; 417 418 for (int row = 0; row < rowCount; ++row) { 419 for (int col = 0; col < colCount; ++col) { 420 421 retVal *= elements[row][col]; 422 } 423 } 424 425 return retVal; 426 } 427 428 /** 429 * Ensures passed two-dimensional array is valid 430 * for initializing a <CODE>Matrix</CODE> object. 431 */ 432 private static void checkValidity(int[][] val) { 433 434 try { 435 int rows = val.length; 436 if (rows == 0) { 437 throw new IllegalArgumentException(); 438 } 439 int cols = val[0].length; 440 if (cols == 0) { 441 throw new IllegalArgumentException(); 442 } 443 for (int i = 1; i < rows; ++i) { 444 if (val[i].length != cols) { 445 throw new IllegalArgumentException(); 446 } 447 } 448 } 449 catch (NullPointerException e) { 450 throw new IllegalArgumentException(); 451 } 452 } 453 454 /** 455 * Ensures passed row and column represent valid indices into 456 * this <CODE>Matrix</CODE>. 457 */ 458 private void checkIndices(int row, int col) { 459 if (row >= rowCount || row < 0 || col >= colCount || col < 0) { 460 throw new IndexOutOfBoundsException(); 461 } 462 } 463 }
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.
Now, you may be saying that this is obvious. That I was simply factoring out duplicate code into a single method that everyone calls. That's true, but when you perform an object-oriented design, you in effect do this code-to-data refactoring ahead of time.
During the initial stages of a object-oriented design, you discover objects. You assign to each object a general area of responsibility. For each area of responsibility, you flesh out what services should be provided by the type of object fulfilling those responsibilities. Finally, you design the interfaces through which objects provide their services to clients. In the process, you are effectively moving code to data.
For example,
you might decide that in your solution there will be a Matrix
object, whose
area of responsibility is matrix
mathematics. As you flesh out the details, you decide that the Matrix
class will be responsible for matrix addition, matrix subtraction, and scalar and matrix multiplication.
You then design an interface through which the Matrix
will fulfill its responsibilities,
such as the interface of the service-oriented Matrix
class shown in Listing 2-3.
By discovering the service-oriented Matrix
in the initial design phase, rather than first starting
with the data-oriented Matrix
and later refactoring towards the service-oriented
Matrix
, you in effect moved
code to data during the design process.
In Guideline 1, I mentioned that one way object-oriented programming helps programmers manage
complexity is it enables them to think more in terms of the problem
domain. However, often when you would do something to an object in the real world, you
are more likely to ask an object to do that thing to itself in an object-oriented program.
For example, in the real world you might multiply a matrix by -1, but in an object-oriented program,
you might instead ask a Matrix
object to multiply
itself by -1.
The reason you would ask a Matrix
to multiply
itself by -1 is because matrix
multiplication involves matrix data. Therefore, the code that represents the matrix multiplication know-how
belongs in the very class that has the matrix data, the Matrix
class itself.
Data-oriented methods, such as the get
and set
that appear
in Listing 2-1, are not inherently bad.
They are certainly appropriate in many situations. The service-oriented Matrix
shown
in Listing 2-3, for
example, still offers three get
methods. Plain old get(int, int)
returns
an element value for the passed row and column. The other two methods, getRows
and
getCols
, return the number of rows and columns in the matrix.
Nevertheless, the best mindset to maintain when designing object methods is to think service-oriented. In general, design methods for your objects that do something interesting with the object's data, something more than just providing clients with access to the data. In the process, you will be moving the code that knows how to manipulate data to the object that contains the data. Moving code to data gives you one of the prime benefits of object-oriented approach to programming: a shot at robustness.
It is easier to achieve system robustness when you move code to data in the system design. I have found in my own development experiences that system robustness requires composing the system out of robust parts. In an object-oriented system, those robust parts are objects. I often feel that robustness is almost an emergent propery of building the system out of robust objects. Each object acts an tiny island of uncorruptible data. And thousands of little islands of uncorrupted data yields a robust system.
Sponsored Links
|