http://buttercola.blogspot.com/2014/11/knowledge-object-oriented-oo.html
Polymorphism is the ability of one method to have different behaviors depending on the type of the object it is being called on or the type of object passed as a parameter.
Action Reuse
Parameterized Actions
Context reuse can be a really useful way to abstract your code from the lower details of API's like JDBC, the IO or NIO API's and others. Especially if the API contains resources that need to be managed (opened and closed, obtained and given back etc.).
http://tutorials.jenkov.com/ood/understanding-dependencies.html
Two classes that uses each other are called "coupled".
Why are Dependencies Bad?
Direct and Indirect Dependencies
What is polymorphism? difference between inheritance and polymorphism
Inheritance refers to using the structure and behavior of a superclass in a subclass. Polymorphism refers to changing the behavior of a superclass in the subclass.
Polymorphism is the ability of one method to have different behaviors depending on the type of the object it is being called on or the type of object passed as a parameter.
Inheritance is when a 'class' derives from an existing 'class'. So if you have a Person class, then you have a Student class that extends Person, Student inherits all the things that Person has.
Java Overloading vs Overriding
Overloading is the situation that two or more methods in the same class have the same name but different arguments.
Overriding means having two methods with the same method name and arguments (i.e., method signature). One of them is in the Parent class and the other is in the Child class.
http://tutorials.jenkov.com/ood/code-reuse-action-and-context-reuse.htmlAction Reuse
Parameterized Actions
private List readUserList(String sql, String[] parameters){ Connection connection = null; List users = new ArrayList(); try{ connection = openConnection(); PreparedStatement statement = connection.prepareStatement(sql); for(int i=0; i < parameters.length; i++){ statement.setString(i, parameters[i]); } ResultSet result = statement.executeQuery(); while(result.next()){ users.add(readUser(result)) } result.close(); statement.close(); return users; } catch(SQLException e){ //ignore for now } finally { //ignore for now } }
public List readAllUsers(){ return readUserList("select * from users", new String[]{}); } public List readUsersWithStatus(String status){ return readUserList("select * from users", new String[]{status}); }Context Reuse
The actions the two methods to with the streams are different. But the context around these actions is the same. The context is iterating and closing an InputStream. The context code is marked in bold in the code examples.
As you can see the context involves exception handling and guaranteed correct closing of the stream after iteration. Writing such error handling and resource releasing time and again is tedious and error prone. Error handling and correct connection handling is even more complicated in JDBC transactions. It would be easier to write this once and reuse it everywhere.
Fortunately the recipe for encapsulating contexts is simple. The context itself is put into its own class. The action, which is what differs between uses of the context, is abstracted away behind an action interface. Each action is then encapsulated in its own class which implements the action interface. A context then needs an action instance plugged in. This can be done by passing the action instance as parameter to the context object constructor, or by passing the action instance as parameter to the context's execute method.
Below is shown how the above examples can be separated into a context plus actions. A StreamProcessor (action) is passed as parameter to the StreamProcessorContext's processStream() method.
//the plugin interface public interface StreamProcessor { public void process(int input); } //the stream processing context class public class StreamProcessorContext{ public void processStream(InputStream inputStream, StreamProcessor processor) throws IOException { if(inputStream == null) return; IOException exception = null; try{ int character = inputStream.read(); while(character != -1){ processor.process(character); character = inputStream.read(); } } finally { try{ inputStream.close(); } catch (IOException e){ if(exception == null) throw e; throw exception; } } } }
Now the StreamProcessorContext class can be used to print out the stream contents like this:
FileInputStream inputStream = new FileInputStream("myFile"); new StreamProcessorContext() .processStream(inputStream, new StreamProcessor(){ public void process(int input){ System.out.print((char) input); } });
... or to read the stream input into a String like this:
public class StreamToStringReader implements StreamProcessor{ private StringBuffer buffer = new StringBuffer(); public StringBuffer getBuffer(){ return this.buffer; } public void process(int input){ this.buffer.append((char) input); } } FileInputStream inputStream = new FileInputStream("myFile"); StreamToStringReader reader = new StreamToStringReader(); new StreamProcessorContext().processStream(inputStream, reader); //do something with input from stream. reader.getBuffer();
As you can see you can do pretty much anything with the stream by plugging in different StreamProcessor implementations. Once the StreamProcessorContext is fully implemented you'll never have sleepless nights about unclosed streams.
Context reuse is quite powerful and can be used in many other contexts than stream processing. An obvious use case is the correct handling of database connections and transactions (open - process - commit()/rollback() - close()). Other use cases are NIO channel processing, and thread synchronization in critical sections (lock() - access shared resource - unlock()). It could also just be something as simple as turning checked exceptions of an API into unchecked exceptions.
When looking for code in your own projects that are suitable for context reuse, look for this pattern of actions:
- general action before
- special action
- general action after
When you find such patterns the general action before and after are possible candidates for context reuse.
Contexts as Template Methods
Sometimes you would like more than one plugin point in a context. If a context consists of many smaller steps and you want each step of the context to be customizable, you can implement the context as a Template Method. Template Method is a GOF design pattern. Basically Template Method splits an algorithm or protocol into a sequence of steps. A Template Method is typically implemented as a single base class with a method for each step in the algorithm or protocol. To customize any of the steps simply create a class that extends the Template Method base class and override the method of the step you want to customize.
The example below is a JdbcContext implemented as a Template Method. The opening and closing of the connection can be overridden by subclasses to provide custom behaviour. The methods processRecord(ResultSet result) must always be overridden as it is abstract. This method provides the behaviour that is not part of the context. The behaviour that is different in each situation in which the JdbcContext is used.
public abstract class JdbcContext { DataSource dataSource = null; /* no-arg constructor can be useful for subclasses that do not need a DataSource to obtain a connection*/ public JdbcContext() { } public JdbcContext(DataSource dataSource){ this.dataSource = dataSource; } protected Connection openConnection() throws SQLException{ return dataSource.getConnection(); } protected void closeConnection(Connection connection) throws SQLException{ connection.close(); } protected abstract processRecord(ResultSet result) throws SQLException ; public void execute(String sql, Object[] parameters) throws SQLException { Connection connection = null; PreparedStatement statement = null; ResultSet result = null; try{ connection = openConnection(); statement = connection.prepareStatement(sql); for(int i=0; i < parameters.length; i++){ statement.setObject(i, parameters[i]); } result = statement.executeQuery(); while(result.next()){ processRecord(result); } } finally { if(result != null){ try{ result.close(); } catch(SQLException e) { /* ignore */ } } if(statement != null){ try{ statement.close(); } catch(SQLException e) { /* ignore */ } } if(connection != null){ closeConnection(connection); } } } }
Here is a subclass that extends the JdbcContext to read a list of users.
public class ReadUsers extends JdbcContext{ List users = new ArrayList(); public ReadUsers(DataSource dataSource){ super(dataSource); } public List getUsers() { return this.users; } protected void processRecord(ResultSet result){ User user = new User(); user.setName (result.getString("name")); user.setEmail(result.getString("email")); users.add(user); } }
Here is how to use the ReadUsers class.
ReadUsers readUsers = new ReadUsers(dataSource); readUsers.execute("select * from users", new Object[0]); List users = readUsers.getUsers();
If the ReadUsers class needed to obtain a connection from a connection pool and release it back to that connection pool after use, this could have been plugged in by overriding the openConnection() and closeConnection(Connection connection) methods.
Notice how the action code is plugged in via method overriding. Subclasses of JdbcContext override the processRecord method to provide special record processing. In the StreamContext example the action code was encapsulated in a separate object and provided as a method parameter. An object implementing an action interface, the StreamProcessor, was passed to the StreamContext's processStream(...) method as parameter.
You can use both techniques when implementing contexts. The JdbcContext class could also have been implemented to take an ConnectionOpener and a ConnectionCloser object as parameters to the execute method, or as parameters to the constructor. Personally I prefer using the separate action object and an action interface for two reasons. First, it makes the action code easier to unit test separately. Second, it makes the action code reusable across multiple contexts. This is of course only an advantage if the action is used in more than one place in the code. After all, it is the context we are trying to reuse here, not the action. You never know, however, if the action may one day be useful inside a different context. Software maintenance has a way of pulling code in directions it was never intended to go in the first place.
The popular Spring framework contains lots of context reuse. For instance Springs JDBC abstraction. The Spring developers refer to their use of context reuse as "inversion of control". This is not the only type of inversion of control used by the Spring framework. A central feature of Spring is the dependency injection bean factories or "application contexts". Dependency injection is another type of inversion of control.
Whenever a class A uses another class or interface B, then A depends on B. A cannot carry out it's work without B, and A cannot be reused without also reusing B. In such a situation the class A is called the "dependant" and the class or interface B is called the "dependency". A dependant depends on its dependencies.
Why are Dependencies Bad?
Dependencies are bad because they decrease reuse. Decreased reuse is bad for many reasons. Often reuse has positive impact on development speed, code quality, code readability etc.
How dependencies can hurt reuse is best illustrated by an example:
Imagine you have a class CalendarReader that is able to read a calendar event list from an XML file. The implementation of CalendarReader is sketched below:
public class CalendarReader { public List readCalendarEvents(File calendarEventFile){ //open InputStream from File and read calendar events. } }
The method readCalendarEvents takes a File object as parameter. Thus this method depends on the File class. This dependency on the File class means that the CalendarReader is capable only of reading calendar events from local files in the file system. It cannot read calendar event files from a network connection, a database or a from a resource on the classpath. You can say that the CalendarReader is tightly coupled to the File class and thereby the local file system.
A less tightly coupled implementation would be to exchange the File parameter with an InputStream parameter, as sketched below:
public class CalendarReader { public List readCalendarEvents(InputStream calendarEventFile){ //read calendar events from InputStream } }
As you may know, an InputStream can be obtained from either a File object, a network Socket, a URLConnection class, a Class object (Class.getResourceAsStream(String name)), a column in a database via JDBC etc. Now the CalendarReader is not coupled to the local file system anymore. It can read calendar event files from many different sources.
With the InputStream version of the readCalendarEvents() method the CalendarReader has become more reusable. The tight coupling to the local file system has been removed. Instead it has been replaced with a dependency on the InputStream class. The InputStream dependency is more flexible than the File class dependency, but that doesn't mean that the CalendarReader is 100% reusable. It still cannot easily read data from a NIO Channel, for instance.
Method or field dependencies are dependencies on concrete methods or fields of an object. It doesn't matter what the class of the object is, or what interfaces it implements, as long as it has a method or field of the required type. The following method illustrates a method dependency. The method depends on a method called "getFileName" in the class of the object given as parameter (fileNameContainer). Note, the dependency isn't visible from the method declaration!
public byte[] readFileContents(Object fileNameContainer){ Method method = fileNameContainer .getClass() .getMethod("getFileName", null); String fileName = method.invoke(fileNameContainer, null); //open the file and return the contents as a byte array. }
Method or field dependencies are common in API's that use reflection to obtain it's goals.
Method (or "function") dependencies can also be seen in languages that support function pointers or method pointers to be passed as parameters to other methods. For instance, C# Delegates.
Dependencies have other important characteristics than just the type. Dependencies can be compile-time, runtime, visible, hidden, direct, indirect, contextual etc. These additional dependency characteristics will be covered in the following sections.
Interface Implementation Dependencies
The more methods an interface has, the less chance there is that developers will provide their own implementation for that interface, unless they are required to. Therefore, the more methods an interface has the larger the probability is that developers will just stick to the default implementation of that interface. In other words, the larger and more complex an interface becomes, the tighter it is coupled to its default implementation!
Because of interface implementation dependencies, you should not add functionality to an interface blindly. If the functionality could be encapsulated in its own component, behind its own interface, you should do so.
Below is an example of what this means. The code example shows a tree node for a hierarchical tree structure.
public interface ITreeNode { public void addChild(ITreeNode node); public List<ITreeNode> getChildren(); public ITreeNode getParent(); }
Imagine that you want to be able to count descendents of a given node. At first you might be tempted to add a
countDescendents()
method to the ITreeNode
interface. However, if you do so anyone who wants to implement the ITreeNode
interface will also have to implement the countDescendent()
method.
Instead you could implement a
DescendentCounter
class that can traverse an ITreeNode
instance an count all descendents of that instance. This DescendentCounter
can be reused with different implementations of the ITreeNode
interface. You have just saved your users the trouble of implementing thecountDescendents()
method, even if they need to implement the ITreeNode
interface!
A dependency that can be resolved at compile time is a compile-time dependency. A dependency that cannot be resolved until runtime is a runtime dependency. Compile-time dependencies tend to be easier for developers to see than runtime dependencies, but sometimes runtime dependencies can be more flexible.
A visible dependency is a dependency that developers can see from a class's interface. If a dependency cannot be seen from the class's interface, it is a hidden dependency.
Another example of a hidden dependency is the dependency on a static singleton, or static methods from within a method. You cannot see from the interface if a class depends on static methods or static singletons.
As you can imagine hidden dependencies can be bad. They are hard to detect for developers using the classes with the hidden dependencies. They can only see them by inspecting the code.
This is not the same as saying that you should never use hidden dependencies. Hidden dependencies are often the result of providing sensible defaults. For instance, in this example it may not be a problem:
public class MyComponent{ protected MyDependency dependency = null; public MyComponent(){ this.dependency = new MyDefaultImpl(); } public MyComponent(MyDependency dependency){ this.dependency = dependency; } }
MyComponent
has a hidden dependency on MyDefaultImpl
as you can see in the first constructor. But ifMyDefaultImpl
does not have any dangerous side effects, then this hidden dependency is not dangerous.Unnecessarily Extensive Dependencies
Sometimes components depend on more information than they need to carry out their job. For instance, imagine a login component for a web application. The login component needs only a user name and a password, and will return the user object, if any, that matches these. The interface could look like this:
public class LoginManager{ public User login(HttpServletRequest request){ String user = request.getParameter("user"); String password = request.getParameter("password"); //read user and return it. } }
Calling the component would look like this:
LoginManager loginManager = new LoginManager(); User user = loginManager.login(request);
It looks simple, right? And even if the login method needs more parameters, you don't need to change the calling code.
But the login method now has what I call an "unnecessarily extensive dependency" on the
HttpServletRequest
interface. It depends on more than it needs to carry out its work. The LoginManager
only needs a user name and a password to lookup a user, but takes a HttpServletRequest
as parameter for the login method. An HttpServletRequest
contains a lot more information than the LoginManager
needs.
The dependency on the
HttpServletRequest
interface causes two problems:- The LoginManager cannot be reused (called) without an
HttpServletRequest
instance. This can make unit testing of theLoginManager
harder. You will need a mockHttpServletRequest
instance, which could be a lot of work. - The LoginManager requires the names of the user name and password parameters to be called "user" and "password". This is also an unnecessary dependency.
A much better interface for the LoginManager's login method would be:
public User login(String user, String password){ //read user and return it. }
Local and Context Dependencies
When developing applications it is normal to break the application into minor components. Some of these components are general purpose components, which could be useful in other applications too. Other components are application specific and are not of any use outside of the application.
For a general purpose component any classes belonging to the component (or API) are "local". The rest of the application is the "context". If a general purpose component depends on application-specific classes, this is called a "context dependency". Context dependencies are bad because it makes the general purpose component unusable outside of the application too.
Standard vs. Custom Class/Interface Dependencies
In many situations it is better for a component to depend on a class or interface from the standard Java (or C# etc.) packages. These classes and interfaces are always available to anyone, making it easier to satisfy these component dependencies. In addition the these classes are less likely to change and cause your application to fail compilation.
In some situations though, depending on JDK classes is not the best thing to do. For example, lets say a method needs 4 strings for its configuration. Then you method takes 4 strings as parameters. An example could be the driver name, database url, user name and password needed for a database connection. If all of these 4 strings are always used together, it may be clearer for the users of that method if you group the 4 strings into a class, and pass instances of the class around, instead of the 4 strings.
Interface implementation dependencies are more common than you would think. I have seen them in many applications and API's. Try to limit them as much as possible, by keeping interfaces small. At least the interfaces you may expect the user of the component to implement themselves. Move additional functionality (like counting etc.) to external components that take an instance of the interface in question as parameter.