Proper abstraction level makes difference. 博客分类: OO* iBATISSQLDAOHibernateJDBC
程序员文章站
2024-03-07 11:55:39
...
Programming is an art, the same solution to a problem could have different versions of coding. Enterprise programming, due to its large code base, is about to provide functionalities while managing code dependencies and data encapsulation. The essence of managing dependencies and encapsulation is abstraction.
I have a terrible case of abstraction at work. We built a data access layer similar to iBATIS and Hibernate. But the abstraction is not quite generic enough(I think the abstraction level is too high, too close to the application, not close enough to JDBC/SQL) so that whenever we modify something, it breaks many others.
The intention is good - we want to reuse existing code. In general, if we abstract some logic at the wrong level, but not far off, we can still get along because there is not a lot of modifications beneath the abstraction. But in this case, where we work with generated SQL, which is highly optimizable, we get a hard hit. This is because below the abstraction, the SQL are connected more tightly than JAVA code, and for performance reason, we need to tune SQL all the time. So whenever we modify one place, the same modified code to generate SQL are used in a lot of places and potential creates bugs, because the modification, in the worst case, only applies to a particular case - meaning the abstraction fails. This results the entanglement between code reuse and vertical modification and optimization.
A better solution is that we don't abstract horizontally at the higher level(the DAO level), we abstract it at the lower level, like iBATIS does, right above JDBC, horizontally, so that we could reuse code. Then abstract the DAO layer vertically. Then the DAO can change vertically without affecting other adjacent vertical DAO abstractions. Furthermore, this is align with DDD as well, so it's easy to adopt new business requirements(new feature, modification of existing features, optimization).
This is the case where one abstraction is not enough, we need two. The first one is more system related, the second one is more application related.
This is a very interesting case because a small mistake gets amplified to an unbearable degree. Although Rod Johnson wrote his book Expert 1-1 J2EE programming in 2002 about this, we still made the same mistake in 2006. The saying says, those who do not understand history are condemned to repeat it.
[Second example]
The post
The summation is really a duplicated code, so we may create a separate method to sum up an array of numbers. A simple class would do the trick:
Though internally we are using the method doubleValue(), this method works for Integer arrays too. Here is the testing class:
The summation is a property of numbers, so the abstraction should be at the Number level.
I have a terrible case of abstraction at work. We built a data access layer similar to iBATIS and Hibernate. But the abstraction is not quite generic enough(I think the abstraction level is too high, too close to the application, not close enough to JDBC/SQL) so that whenever we modify something, it breaks many others.
The intention is good - we want to reuse existing code. In general, if we abstract some logic at the wrong level, but not far off, we can still get along because there is not a lot of modifications beneath the abstraction. But in this case, where we work with generated SQL, which is highly optimizable, we get a hard hit. This is because below the abstraction, the SQL are connected more tightly than JAVA code, and for performance reason, we need to tune SQL all the time. So whenever we modify one place, the same modified code to generate SQL are used in a lot of places and potential creates bugs, because the modification, in the worst case, only applies to a particular case - meaning the abstraction fails. This results the entanglement between code reuse and vertical modification and optimization.
A better solution is that we don't abstract horizontally at the higher level(the DAO level), we abstract it at the lower level, like iBATIS does, right above JDBC, horizontally, so that we could reuse code. Then abstract the DAO layer vertically. Then the DAO can change vertically without affecting other adjacent vertical DAO abstractions. Furthermore, this is align with DDD as well, so it's easy to adopt new business requirements(new feature, modification of existing features, optimization).
This is the case where one abstraction is not enough, we need two. The first one is more system related, the second one is more application related.
This is a very interesting case because a small mistake gets amplified to an unbearable degree. Although Rod Johnson wrote his book Expert 1-1 J2EE programming in 2002 about this, we still made the same mistake in 2006. The saying says, those who do not understand history are condemned to repeat it.
[Second example]
The post
http://www.iteye.com/topic/39694?has a discussion on how to remove duplicated code. There are several ideas and complains on Java. I think the problem here is again how to abstract the code properly for reuse. If we look close the code, there are two duplicates: one is the getters on several properties, one is to sum the values. Reflection is proposed to remove duplicated getter calls. My question is: are these getter calls really duplicated? They may look syntactically similar, but they are semantically different, namely, they are totally different in the domain. So I think we should *not* consider them duplicated code and try to use reflections. An immediate consequence of using reflection on these getters is refactoring. What if we want to rename a field, the IDE won't find the usage in the reflection. What if we want to group some fields to a more meaningful class, say we have street number, street name, city name, and we want to group them into a separate class Address, then the reflection is broken.
The summation is really a duplicated code, so we may create a separate method to sum up an array of numbers. A simple class would do the trick:
java 代码
- public class Summation
- {
- public Number sum(Number[] numbers)
- {
- double sum = 0.0;
- for (int i=0; i<numbers.length; i++)
- {
- sum += numbers[i].doubleValue();
- }
- return new Double(sum);
- }
- }
Though internally we are using the method doubleValue(), this method works for Integer arrays too. Here is the testing class:
java 代码
- public class SummationTest
- {
- private Summation sumOperation = new Summation();
- public static void main(String args[])
- {
- SummationTest test = new SummationTest();
- test.testDoubles();
- test.testfloats();
- test.testLongs();
- test.testIntegers();
- test.testShorts();
- }
- public void testDoubles()
- {
- Number[] inputs = new Number[] { new Double(2.4), new Double(5.8) };
- Number result = sumOperation.sum(inputs);
- print(result);
- }
- public void testfloats()
- {
- Number[] inputs = new Number[] { new Float(2.4), new Float(5.8) };
- Number result = sumOperation.sum(inputs);
- print(result);
- }
- public void testLongs()
- {
- Number[] inputs = new Number[] { new Long(2), new Long(3) };
- Number result = sumOperation.sum(inputs);
- print(result);
- }
- public void testIntegers()
- {
- Number[] inputs = new Number[] { new Integer(2), new Integer(3) };
- Number result = sumOperation.sum(inputs);
- print(result);
- }
- public void testShorts()
- {
- Number[] inputs = new Number[] { new Short((short)2), new Short((short)3) };
- Number result = sumOperation.sum(inputs);
- print(result);
- }
- private void print(Number number)
- {
- System.out.println("----------------------------------------------");
- System.out.println("double=" + number.doubleValue());
- System.out.println("float=" + number.floatValue());
- System.out.println("long=" + number.longValue());
- System.out.println("int=" + number.intValue());
- System.out.println("short=" + number.shortValue());
- System.out.println("byte=" + number.byteValue());
- }
- }
The summation is a property of numbers, so the abstraction should be at the Number level.