05
-
5.0 Customer Projects Application
- 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:
- creates a "repository" object which utilises a "DAO" to load customer details and build the customer collection
- displays customer collection details
- utilises" toString()" methods
- displays menu and accepts user choice
- calls add, remove or store methods dependent on choice
The following class diagrams details aspects of a system's architecture: -
5.1 Class Breakdown & Package Organisation
- methods:
- main()
- run()
- responsibility:
- create and run controller object
- attributes:
- customer collection (repository)
- methods:
- CustomerProjectsController()
- run()
- displayCustomerProjectsMenu()
- addProjectToCustomer()
- removeProjectFromCustomer()
- responsibilities:
- create repository
- implement app functionality
- (use case functionality)
- attributes:
- items (List)
- methods:
- Repository()
- getItems() & setItems()
- getItem()
- add() & remove()
- toString()
- store()
- responsibilities:
- create data access object to load from file
- manage repository in response to controller requests
- retrieve items, add new item, remove item
- create data access object to store to file
- attributes:
- delimiter (constant)
- methods:
- load()
- store()
- stripQuotes()
- responsibilities:
- implement DAOInterface
- i.e. provide versions of load() & store() which work with delimited text files
- load data from file and return a repository object
- Store data from a repository object into a file
- 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
Package Organisation
-
5.2 Interface
- The methods of an object define its interface with the rest of the world; this can be formalized into an "Interface" definition which provides a formal contract. The interface contains a set of shared methods with empty bodies. A class is used to implement the interface(s) and it must provide (or inherit) a method for each method defined in the interface(s) it implements. An Interface can be used to define a set of methods which can be shared by a number of classes, e.g. common operations to be performed by different collection classes.
- We have already seen the use of the Serializable interface:
-
public class Policy implements Serializable {
- 🔗 Java Serializable
- An Interface defines an abstract type which is not instantiated but implemented. It supports the Separation of Concerns design pattern which aids a “divide and conquer” approach, i.e. breaking a problem down into a number of manageable chunks. Interfaces can be used to simulate multiple inheritance, i.e. we can only ever inherit from one class at a time but we can inherit from more than one interface provided the class implements each method of each specified interface. However, this can be problematic, or cause confusion, if we inherit multiple copies of functionality.
- It is preferable that an Interface is implemented by more than one class to justify its existence However, an interface approach can provide a cleaner design concentrating on method signatures rather than implementation.
-
5.3 Repository Interface
- We can use the refractor extract interface functionality to generate an interface:
- Interfaces provide a contract definition which can be used in the development of different classes which require common functionality
- You can provide a default implementation for a method in the Interface class:
-
public interface TimeClient { void setTime(int hour, int minute, int second); void setDate(int day, int month, int year); default ZonedDateTime getZonedDateTime(String zoneString) { return ZonedDateTime.of(getLocalDateTime(), getZoneId(zoneString)); } }
-
5.4 Abstract Classes and Methods
- An abstract method is a method defined without an implementation e.g. in an interface class. Note that the abstract keyword is not needed for interface method definitions as they are implicitly abstract.
-
abstract void moveTo(double deltaX, double deltaY);
- An abstract class is a class which cannot be instantiated but exists to define common fields and methods which all its subclasses inherit.
-
public abstract class GraphicObject {}
- However, it does not necessarily need contain abstract methods although typically it will with subclasses overriding the methods to provide implementation. There is a similarity between an abstract class and an interface class in that neither can be instantiated. All fields defined in an interface are automatically public while an abstract class can define private and protected members. A class extends only one class but can implement a number of interfaces.
- 🔗 Java Abstract
-
5.5 Example: Customer Projects App (revisited)
- 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
- A Project object defines project id, title and start date
- A Project is either Planned or Ad Hoc
- A PlannedProject has a set of employees and a no. of allocated days
- An AdHocProject has a single employee
- Since a Project object will either be a PlannedProject object or an AdHocProject then there is never a need to instantiate the Project class, i.e. we can make it an abstract class. It can be used to specify attributes and methods that every type of project has
-
public abstract class Project { protected String projectId; protected String projectTitle; protected Calendar startDate;
Class Diagram -
5.6 Nested Classes
- Java provides a number of options for defining a class. Sometimes a class is so intrinsically linked with another class that objects can’t exist except as part of an object of the other class. In such cases, following the encapsulation principle, it makes sense to define the second class inside the first class "a nested class".
- For example, a collection may be made up of items where the items only have an existence as part of the collection. Consequently, the item class can be defined inside the collection class. This approach allows us to logically group classes only used in one place.
- As previously mentioned Java allows us to nest classes, i.e. declare a class within another class. Such classes can be public, private, protected or package private. This is useful where objects of the nested class only exist as part of objects of the outer class. This allows us to restrict visibility, aid encapsulation and enhance readability & maintainability. There are two types or nested classes:
- Static nested classes
- No direct access to members of outer class
- Inner classes (non-static)
- Direct access to fields and methods of outer class
-
class OuterClass { ... static class StaticNestedClass{} class InnerClass{} }
- A static nested class is signified by the use of the static keyword and it in implemented for packaging reasons. This defines a class where there is no need for direct access to fields and methods of outer class. i.e. the nested class instance does not need to know which outer class instance it belongs to and it does not have a reference to the enclosing class or a this reference. It can still access outer class members through an object reference which is identified by including name of outer class.
-
OuterClass.StaticNestedClass nestedObject = new OuterClass.StaticNestedClass();
- Example:
- A NoticeBoard object contains a collection of Post objects
-
public class NoticeBoard { private String name; private ArrayList<Post> posts;
- Since post objects are intrinsically linked with their NoticeBoard object it makes sense to define the Post class inside the NoticeBoard class.
-
public class NoticeBoard { private String name; private ArrayList<Post> posts; private static class Post { private int postId; private String postTitle; private Calendar postDate; private String postBody; private static int numberOfPosts = 0;
- Note the use of the static keyword in the definition of the Post class and that it is a private class within the NoticeBoard class so access to it is only through methods of the NoticeBoard class
-
private static class Post {
- Also note the use of the static class variable in the class to keep track of the number of Posts created
-
private static int numberOfPosts = 0;
- Let’s now consider the methods of both the NoticeBoard and Post classes
-
public NoticeBoard() { this.name="Not Supplied"; this.posts = new ArrayList<>(); } public NoticeBoard(String name) { this.name=name; this.posts = new ArrayList<>(); } public String getName() { return this.name; } public void setName(String name) { this.name = name; }
- We also need methods to handle Posts:
-
private ArrayList<Post> getPosts() { return this.posts; } public void addPost(String title, String body) { posts.add(new Post(title, body)); } public String toString() { return name + "\n" + getPosts() ; }
- Note the "addPost()" method creates a new "Post" object with the supplied title and body and then adds this object to the posts collection of the "NoticeBoard" object:
-
posts.add(new Post(title, body));
- The "Post" class simply defines the fields of a "Post" object, constructors, getters, setters and a "toString()" method:
-
private static class Post { private int postId; private String postTitle; private Calendar postDate; private String postBody; private static int numberOfPosts = 0; public Post(String title, String body) { this.postId = ++numberOfPosts; this.postTitle = title; this.postDate = Calendar.getInstance(); this.postBody = body; } public int getPostId() { return this.postId; } public String getPostTitle() { return this.postTitle; } public void setPostTitle(String title) { … } public Calendar getPostDate() { … } public void setPostDate(Calendar postDate) { … } String getPostBody() { … } public void setPostBody(String body) { … } private String getDateString() { SimpleDateFormat formatter = new SimpleDateFormat("yyyy MMM dd hh:mm:ss"); String dateString = formatter.format(postDate.getTime()); return dateString; } @Override public String toString() { return "\n" + Integer.toString(postId) + " : " + postTitle + " : “ + getDateString() + " : " + postBody; }
- In this scenario an app does not know about the "Post" class (i.e. post is something we do with a notice board) and simply adds a new post to the collection.
-
NoticeBoard noticeBoard = new NoticeBoard("Programming 2"); noticeBoard.addPost("Lecture 1", "This lecture covers revision material");
- Therefore it makes sense to not only declare the Post class nested inside the NoticeBoard class but also make it private to that outer class. You can make nested classes public but refer to the class using the outer class name
-
Order.OrderLine newOrderLine = new Order.OrderLine(itemId, quantity); customerOrder.add(newOrderLine);
-
5.7 Inner (non-static) Classes
- An Inner class is a nested class which can access the members of its outer class, however it cannot define static members itself. Again an instance of the inner class only exists within an instance of the outer class, i.e. to instantiate an inner class you must first instantiate an outer class object. It is distinguished from a static nested class in that it has direct access to the methods and fields of its outer class; each inner class object has a (hidden) reference to an object of the outer class.
- Consider the Notice Board scenario:
- let’s add a collection of subscribers to the "NoticeBoard" class. Again a "Subscriber" object is intrinsically linked to the "NoticeBoard" class so we define it as a nested class. Subscription to the Notice Board involves creating a "Subscriber" object and adding it to the "subscribers" collection field of the "NoticeBoard" object (as with "posts"). However a "Subscriber" object may choose to unsubscribe and it makes sense that this is an operation of the Subscriber object rather than the "NoticeBoard" object. This method would still need to remove itself from the "subscribers" field of the "NoticeBoard" object hence the need for direct access to the fields of its outer class i.e. "NoticeBoard".
-
public class NoticeBoard { private String name; private ArrayList<Post> posts; private ArrayList<Subscriber> subscribers; private static int numberOfSubscribers = 0; public NoticeBoard(String name) { this.name=name; this.posts = new ArrayList<>(); this.subscribers = new ArrayList<>(); }
- Note the class variable "numberOfSubscribers"; this cannot be declared inside the "Subscriber" class as it is "static"
-
public class Subscriber { private int subscriberId; private String subscriberName; private Calendar dateSubscribed; private String email; public Subscriber(String name, String email) { this.subscriberId = ++numberOfSubscribers; this.subscriberName = name; this.dateSubscribed = Calendar.getInstance(); this.email = email; subscribers.add(this); }
- Note the direct access to the class variable "numberOfSubscribers" and the field subscribers of the outer class "NoticeBoard". Adding a subscriber to a "NoticeBoard" object is accomplished in the same way as adding a post:
- App:
-
NoticeBoard.Subscriber s = noticeBoard.addSubscriber("Andres", "andy@barca.com");
- Note the app can define a "Subscriber" object as the "Subscriber" class is declared public, however it must qualify the class name with its outer class
- NoticeBoard:
-
public Subscriber addSubscriber(String name, String email) { Subscriber s = new Subscriber(name, email); return s; }
- Note there is no need for this method to add the new "Subscriber" object to the "subscriptions" collection as the constructor for the "Subscriber" class does this.
- Recall that we discussed the unsubscription process as being initiated by a subscriber and so it makes sense that the process is carried out by a "Subscriber" method
- App:
- s.unsubscribe();
- Subscriber:
-
public void unsubscribe() { subscribers.remove(this); // NoticeBoard.this.subscribers.remove(this); }
- The "unsubscribe()" method of the inner class, "Subscriber", has direct access to the members of its outer class, "NoticeBoard", and can therefore remove itself from the "subscribers" field of the "NoticeBoard" object.
- Additional Information on Inner Classes
- Serialization of inner classes is strongly discouraged as there maybe compatibility issues with different JRE implementations.
- Two special types of inner class exist:
- Local classes
- Anonymous classes
- Both Local and Anonymous classes are defined within a block and instantiated objects only have meaning and lifetime within that block
- e.g. method body, for loop, if clause etc.Typically these types of classes are declared inside methods where an object is required but only for the lifetime of the execution of the method and the object class is not used anywhere else
- Typically a local class is used in a method where the method requires to execute methods defined in an interface
- Since the interface cannot be instantiated an object must be created to execute the methods
- The class of the object is only needed within the method and implements the interface
- Advantages including hiding the name of the class inside the scope of the method
- The methods of the class can access variables from the enclosing scope
-
5.8 Local Class Example
- The application scenario involves asking the user to specify a language and a number and outputting a translation message
-
public static void main(String[] args) { System.out.println("1. English, 2: Francais, 3: Espanol"); int languageChoice = readInt("Language", 3, 1); Language language = new Language(languageChoice); int number = readInt("Enter number", 10, 1);
- Note the creation of a Language object with the "language" choice, i.e. the Language class has a constructor which accepts an integer parameter and sets a field value indicating which language is required
-
public class Language { private static String[] greeting={"Hello", "Bonjour", "Hola"}; private static String[] numberString={"your number is", "votre numéro est", "su número es"}; private static String[] goodbye={"Goodbye", "Adieu", "Adios"}; private static String[][] numbers={{"one", "two", …, "ten"}, {"un", "deux", …, "dix"}, {"uno", "dos", …, "diez"}}; private static int language; public Language(int language) { this.language=language; }
- Note the definition of the static array variable to hold the translations, in particular, the 2D array for numbers in the different languages.
- Next we need to implement the method which returns the output String – languageWord()
- App:
-
System.out.println(language.languageWord(number));
- Language:
-
public String languageWord(int number) {
- Outputting the required String will involve running several methods: "greeting()", "goodbye()" and "numberWord()". We have defined these in an interface: "LanguageInterface".
-
public interface LanguageInterface { public String greeting(); public String goodbye(); public String numberWord(int number); }
- An interface cannot be instantiated so the "languageWord()" method needs a class definition in order to instantiate an object which can run each of these methods
-
public String languageWord(int number) { class LanguageWord implements LanguageInterface { @Override public String greeting() { return greeting[language-1]; } @Override public String goodbye() { return goodbye[language-1]; } @Override public String numberWord(int number) { return numberString[language-1] + " " + numbers[language-1][number-1]; } }
- Note the LanguageWord class only exists in order to implement the methods of the interface LanguageInterface and is only ever needed in executing the languageWord() method of the Language class
-
public String languageWord(int number) { class LanguageWord implements LanguageInterface {
- As a non-static inner class it can directly access the members of its outer class
-
return greeting[language-1];
- Now that the method has a class definition it can instantiate an object and then execute the methods of the implemented interface:
-
return lang.greeting() + ", " + lang.numberWord(number) + ", " + lang.goodbye();
-
5.9 Local Class Example (Oracle)
-
public class LocalClassExample { static String regularExpression = "[^0-9]"; public static void validatePhoneNumber( String phoneNumber1, String phoneNumber2) { final int numberLength = 10; class PhoneNumber { String formattedPhoneNumber = null; PhoneNumber(String phoneNumber){ String currentNumber = phoneNumber.replaceAll(regularExpression, ""); if (currentNumber.length() == numberLength) formattedPhoneNumber = currentNumber; else formattedPhoneNumber = null; } public String getNumber() { return formattedPhoneNumber; } } PhoneNumber myNumber1 = new PhoneNumber(phoneNumber1); PhoneNumber myNumber2 = new PhoneNumber(phoneNumber2); if (myNumber1.getNumber() == null) System.out.println("First number is invalid"); else System.out.println("First number is " + myNumber1.getNumber()); } public static void main(String... args) { validatePhoneNumber("123-456-7890", "456-7890"); }
- From SE8 (Java Standard Edition 8) a local class can access local variables and parameters of an enclosing block that are final or effectively final, i.e. never changed within the block after initialized. A local class can have static members provided they are constant variables. Since the class only has one instance and is only defined within the method there is strictly no need for providing a name and a formal class definition for it, we can use an "anonymous" class.
-
-
5.10 Anonymous Classes
- An anonymous class object is created merely to execute a method. It enables more concise code as it can declare and instantiate class at the same time. Use an anonymous class if you only need to instantiate a local class once. Anonymous classes are expressions therefore their definition must be as part of another expression. The anonymous class expression consists of the following:
- The new operator
- The name of an interface to implement or a class to extend
- Parentheses that contain the arguments to a constructor
- Empty parentheses when using an interface as it has no constructor
- A body, which is a class declaration body
- method declarations allowed
- statements not allowed
- can declare fields and other methods
- Can access members of enclosing class
- Final/effectively final local variables
- Often used in graphical user interface (GUI) applications
- In the Language scenario the "languageWord()" method wishes to execute the interface methods "greeting()" , "goodbye()" and "numberWord()". Using a Local Class "LanguageWord", an object is instantiated to execute the methods to construct the return value for the "languageWord()" method. However no other code will be aware of the "LanguageWord" class and its existence is merely to create a temporary object to execute the interface methods. In the Language scenario the "languageWord()" method wishes to execute the interface methods "greeting()" , "goodbye()" and "numberWord()".
- Using a Local Class "LanguageWord", an object is instantiated to execute the methods to construct the return value for the "languageWord()" method. However no other code will be aware of the "LanguageWord" class and its existence is merely to create a temporary object to execute the interface methods. We merely wish to create an object on the fly to execute the methods – there is no need to name it or formally define its class. We can rewrite the languageWord() method to use an anonymous class:
-
public String anonymousLanguageWord(int number) { LanguageInterface langInterface = new LanguageInterface() { @Override public String greeting() { return greeting[language-1]; } @Override public String goodbye() { … } @Override public String numberWord(int number) { … } };
- It looks as though we are instantiating the interface class "LanguageInterface" as the "langInterface" object variable. However we are actually creating an anonymous class which implements the interface. Then instantiating the object "langInterface" of the anonymous class and executing the implemented methods:
-
return langInterface.greeting() + ", " + langInterface.numberWord(number) + ", " + langInterface.goodbye();