04
-
4.0 Architecture
- A programming architecture refers to the underlying structure, or design, of an entire system. More specifically, a programs structure consists of software elements and their interrelationships as well as the properties of the elements and relationships.
- In this module we are using a particular architecture for the applications that we will be developing. There will be an application class that simply creates a controller object and executes its "run()" method. The controller constructor method "Controller()" builds the model (in-memory), i.e. creates objects. During this process the system may use an "InputHelper" object to seek information from the user or it may load the data for the model from file storage.
- The controller "run()" method then:
- displays the current state of the model objects
- displays a menu and accepts user choice
- executes a private method based on the user choice
- typically add and delete options
- persists the model data to file storage
The following class diagrams details aspects of a system's architecture: -
4.1 Customer Projects Application Example
- In this example an app is required to manage customers and their projects. A Customer object defines a customer id, their name and a list of projects that associated with the customer. The application has the following required functionality:
- load customer details from a file into a collection
- display customer collection details
- display a menu and accept use choice
- add a project to a customer
- remove a project from a customer
- store customer collection details to a file
- We can use the same architectural pattern as before (above examples), i.e. create an app class where execution begins (the "main()" method). Then create a run method which calls the constructor of the controller class "Controller()". The controller class will then:
- load customer details to build the customer collection
- display customer collection details
- utilises "toString()" methods
- display menu and accepts user choice
- call add, remove or store methods dependent on the users choice
-
public class Customer { private final int customerId; private String customerName; private ArrayList<String> customerProjects; private static int lastCustomerIdAllocated = 0;
Example class diagram: -
4.2 Class Breakdown (App & Controller)
- methods:
- main()
- run()
- responsibility:
- create and run controller object
- attributes:
- customer collection (repository)
- methods:
- CustomerProjectsController()
- loadCustomersFromTextFile()
- storeCustomersToTextFile()
- run()
- displayCustomerProjectsMenu()
- addProjectToCustomer()
- removeProjectFromCustomer()
- responsibilities:
- load and build model
- implement app functionality
- (use case functionality)
- As we have seen apps require a number of classes. We want to make these classes as cohesive and independent as possible for two reasons. Firstly, it aids reuse of code as opposed to duplication but the main reason is to aid separation of concerns.
- Separation of concerns is a design principle which encourages the development of an app using units (classes) which focus on single aspects of the functionality and overlap as little as possible, i.e. each unit (class) should focus on one clear aspect of the required functionality. Currently our controller class fulfills two aspects, it loads and build the model and it Implement the app’s functionality. Separation of concerns would indicate that we should separate this functionality into distinct classes. Since the controller implements the use case(s) of the required system we should abstract the loading and building of the model into a separate class.
"In software engineering, a software design pattern is a general reusable solution to a commonly occuring problem within a given context in a software design. It is not a finished design that can be transformed directly into source or machine code. It is a description or template for how to solve a problem that can be used in many different situation. Design patterns are formalised best practices that the programmer can use to solve common problems when designing an application ot system." Wikipedia -
4.3 Repository Pattern
- A repository object can be created and used to separate the code that retrieves & persists data (used to populate the model) from the business logic that works on the model. A multitude of authors have described this pattern including Martin Fowler:
- We are going to develop a Repository class and make it as generic as possible.
- Consider the required functionality, we want to be able to:
- define attributes which will represent the model
- typically a collection of model objects
- load data from a file to populate the collection
- in a constructor method
- add an item to the collection
- remove an item from the collection
- retrieve all items from the collection
- retrieve a specified item from the collection
- store the collection to a file
- produce a String version of the state of the collection
- i.e. model objects
- Consequently, we will develop the following class:
- attributes:
- items collection
- methods:
- Repository()
- getItems()
- setItems()
- getItem()
- add()
- remove()
- store()
- toString()
- responsibilities:
- load, build and maintain in-memory model objects
-
public class Repository { private List<Customer> items; public Repository() { this.items = new ArrayList<>(); } public Repository(List<Customer> items) { this.items = items; } public Repository(String filename) { this(); … }
- We can also use a constructor to load the repository from a pre-existing file:
-
public Repository(String filename) { this(); try (BufferedReader br = new BufferedReader(new FileReader(filename))) { String[] temp; String line = br.readLine(); while(line!=null){ temp=line.split(Character.toString(DELIMITER)); int customerId = Integer.parseInt(temp[0]); String customerName = stripQuotes(temp[1]); Customer customer = new Customer(customerId, customerName); int noProjects = Integer.parseInt(temp[2]); for (int i=0; i<noProjects; i++) { String project = stripQuotes(temp[i+3]); customer.addProjectToCustomer(project); } this.items.add(customer); line = br.readLine(); } br.close(); … }
-
public List<Customer> getItems() { return this.items; } public Customer getItem(int id) { for (Customer item:this.items) { if (item.getCustomerId() == id) return item; } return null; } public void add(Customer item) { this.items.add(item); } public void remove(int id) { Predicate<Customer> customerPredicate = c->c.getCustomerId() == id; this.items.removeIf(customerPredicate); }
-
public void store(String filename) { try ( PrintWriter output = new PrintWriter(filename)) { output.print(this.toString(DELIMITER)); output.close(); } … }
- We have written the code as 'generic' as possible to minimize references to Customer to aid reuse. However, ideally we would like a Repository class which could be instantiated with any type of model object.
- Revisiting the design guideline of separation of concerns leads us to the conclusion that this version of the Repository class actually both, loads and stores the model data from/to file and manages the in-memory model objects i.e. collection.
- Let’s consider a pattern which will separate these...
-
4.4 Data Access Object Pattern (DAO)
- A DAO pattern is often used to provide access to persistence mechanisms (e.g. a database), it is commonly described in the context of database access.
- “An object that wraps a row in a database table or view, encapsulates the database access, and adds domain logic on that data” - Martin Fowler
- Persistence mechanisms are characteristics of states that outlive the process that created them. More simply they are characteristics of states that have been archived as stored data. There are several ways in which data can be stored:
- Text Files
- Delimited (CSV)
- XML
- JSON
- Object Files
- Databases
- Relational, Object Oriented, NoSQL
- Our current approach involves generating/amending constructors and store methods for each type of persistence. Other persistence methods would also include other methods and configurations. It would beneficial if we could abstract the persistence issues away from the repository class into dedicated classes. This would allow the repository class to concentrate on fields and methods to access/update the repository, i.e. the collection of model objects. A Data Access Object Pattern provides guidance on how to separate persistence logic from the model. This helps to enforce the separation of concerns guideline where individual classes concentrate on specific pieces of functionality, as well as providing opportunities for reuse as well and a cleaner design.
- 🔗 http://tutorials.jenkov.com/java-persistence/dao-design-pattern.html
- 🔗 http://www.corej2eepatterns.com/Patterns2ndEd/DataAccessObject.htm
- BusinessObject (“Client”)
- represents data client
- object requires access to data source to obtain and store data
- may be implemented as a session bean, entity bean, or POJO
- DataAccessObject
- primary object of pattern
- abstracts underlying data access implementation for BusinessObject
- enable transparent access to data source
- BusinessObject also delegates data load and store operations to DataAccessObject
- DataSource
- data source implementation
- e.g. RDBMS, OODBMS, XML repository, flat file system
- also be another system (legacy/mainframe), service (B2B service or credit card bureau), or some kind of repository (LDAP)
- TransferObject
- used as a data carrier
- DAO may use to return data to client
- DAO may also receive data from client in a Transfer Object to update data in data source
- This approach has both benefits and drawbacks.
- benefits:
- changing the persistence mechanism is limited to changing the DAO
- there is no change to domain logic
- the DAO layer normally contains less classes
- e.g. Hotel & Room classes are incorporated in a HotelDAO
- problems:
- connection scoping
- transaction scoping
- exception handling
"The Data Access Object (DAO) pattern is now a widely accepted mechanism to abstract away the details of persistence in an application. The idea is that instead of having the domain logic communicate directly with the databse, file system, web service, or whatever persistence mechanism your application uses, the domain logic speaks to a DAO layer instead. This DAO layer then communicates with the underlying persistence system or service." -
4.5 Interface
- We are going to define an "Interface" to specify the methods that we need to handle persistence issues, i.e. "load()" and "store()". An Interface is a useful mechanism for doing this as it does not specify how these methods will be implemented, i.e. whether we are using a text file or an object file or other persistence mechanism, in other words we can implement the Interface in many ways. An Interface cannot be instantiated so we will need an implementation class to flesh out the methods and define any attributes.
-
public interface DAOInterface { public Repository load(String filename); public void store(String filename, Repository repository); }
- Again we have written this to be generic ensuring not to mention the Customer class. As noted an interface is generally a list of methods that any implementation class must implement. For a text file implementation we will need to flesh out the load() and store() methods, i.e. move the code from the Repository class.
- The implementation class is said to realize the interface. It use the "implements" keyword and overrides the methods specified in the "Interface" definition:
- public class DAOTextImpl implements DAOInterface {
static final char DELIMITER=','; -
@Override public Repository load(String filename) { Repository repository = new Repository(); … return repository; } @Override public void store(String filename, Repository repository) { … }
- We have abstracted the persistence details from the Repository constructor and store() methods, and we replaced the code with code to create an appropriate dao object from which we can call the load() or store() method as required.
-
public Repository(String filename) { this(); DAOTextImpl dao = new DAOTextImpl(); this.items = dao.load(filename).getItems(); } public void store(String filename) { DAOTextImpl dao = new DAOTextImpl(); dao.store(filename, this); }
- This approach means we can have multiple persistence mechanisms in a program. For example to change the persistence mechanism to object files:
- Create a DAOObjImpl class which implements the DAOInterface
- Change the type of the dao object in the Repository load() and store() methods to this new class
- For this app we have created folders to bring together classes and interfaces which share common functionality and persistence approach
- app
- customerprojects
- model
- repositories
- controllers
- daos
- helpers
Class diagram: