Spring Core
Spring Framework 5 for Java Web Applications
- Testing Code
- Best Coding Practice
- Spring Bean Life Cycle
- Dependency Injection and IoC
- Spring Configuration and StereoTypes
- Reactive Programming
- Docker
- Data Validation with JSR-303
- Exception Handling
- Data Binding in Spring
- Options for Database Intialization
- JPA
- Spring Boot
- Dependency Inversion Principle
- Open Closed Principle
- Interface Segregation Principle
Testing Code
Terminology
- Test Fixture - A fixed state of a set of objects used as a baseline for running tests. There should be a well known and fixed environment in which tests are run so that the results are repeatable.
- Unit Tests / Unit Testing - Code written to test code uder test
- Designed to test specific sections of code
- Ideally code coverage is maybe 70-80%
- Should be small and fast
- No external dependencies
- No databases or spring components
- Integration Tests - Designed to test behaviors between objecrts and parts of the overall system
- Much larger scope
- Can include the Spring Context, databases, and message brokers
- Much slower than unit tests
- Functional Tests - Testing a running application
- Likely deployed in a test env
- Functional touch points are tested
- Test Driven Development - Write Tests first, which of course fail, then code to 'fix' it
- BDD - Specifies that tests of any unit of software should be specified in terms of desired behavior of the unit
- Often implemented with DSLs to create natural language tests
- JBehave, Cucumber, Spock
- Mock - A fake implementation of a class used for testing. Like a test Double.
- Spy - A partial mock, allowing you to override select methods of a real class.
- Generally Mocking is more popular than Spying
Testing Goals
- Generally you want the majority of tests to be unit tests.
- Bringing up the Spring Context makes your test exponentially slower
- Try to test specific business logic in unit tests
- Use integration tests to test interactions
- Think of a pyramid. Base is unit tests, middle is integration, top is functional tests
Test Scope Dependencies
- Using spring-boot-starter-test will load the following:
- JUnit - the de-facto standard for unit testing
- Spring Test and Spring Boot Test - Utilites and integration test support for Spring Boot applications
- AssertJ - fluent assertion library
- Hamcrest - A library of matcher objects
- Mockito - A Java mocking framework
- JSONassert - An assertion library for JSON
- JSONPath - XPath for JSON
JUnit Annotations
Spring Boot Annotations
Best Coding Practice
Naming
- Interfaces
- Should be a good Object Name
- Never start with an 'i' (This is a dot net thing)
- Implementation
- With only 1 implementation: user
+ Impl - With more than one the name should indicate the difference of implementation
- With only 1 implementation: user
SOLID Principals
- Single Responsibillity Principal
- Each class jas a single repsonsibillity
- No more than a screen full of code per class
- Open/Closed Principal
- Classes should be open for extension but closed for modification
- Use private getters and setters ONLY when you need them
- Liskov Substitution Principal
- Objects should be replaceable with instances of their subtypes without altering correctness
- E.g. A sqaure is a rectangle
- Interface Segregation Principal
- Make fine-grained interfaces that are client specific
- Keep components focused and minimize shared dependencies
- Dependency Inversion Principal
- Abstraction should not depend on details
- Important that high and low level objects depend on the same abstract implementation
Spring Bean Life Cycle
- When the class is created we can see there are 'Aware' interfaces that get inialized
- BeanNameAware
- BeanFactoryAware
- ApplicationContextAware
Shutdown
Container Shutdown -> Disponable Bean's destroy() -> Call custom destroy method -> Terminated
Callback Interfaces
- Spring has two interfaces you can implement for call back events
-
InitializingBean.afterPropertiesSet()
- called after properties are set
-
DisposableBean.destroy()
- called during bean destruction in shutdown
Life Cycle Annotations
- Spring has two annotations you can use to hook into the bean life cycle
-
@PostConstruct
- called after the bean has been constructed but before its returned to the requesting object
-
@PreDestroy
- called just before the bean is destroyed by the container
Bean Post Processors
- Gives you a means to tap into the Spring context life cycle and interact with beans as they are processed
- Implement interface BeanPostProcessor
-
postProcessBeforeInitialization
- called before bean initialization method
-
postProcessAfterInitialization
- called after bean initialization
Note: The guru admits in all his years he has never used these
'Aware' Interfaces
- Spring has over 14 Aware Interfaces
- These are used to accses the Spring Framework infrastructure
- Rarely used by Devs, mostly used within the framework itself
The more useful ones:
- ApplicationEventPublisherAware - used for creating custom events inside spring and set up event listeners
- BeanFactoryAware - If you need to handle a bean within a process
Spring Bean Scopes
- Singleton (default) - Only one instance of the bean is created in the IoC container
- Prototype - A new instance is created each time the bean is requested
- Request - Single instance per http request.*
- Session - Single instance per http session.*
- Global-session - A single instance per global session. Typically only used in a Portlet context.*
- Application - bean is scoped to the lifecycle of a ServletContext.*
- Websocket - Scopes a single bean definition to the lifecycle of a WebSocket.*
- Custom scope - extensible, define your own "Scope" interface. See docs for details
*Only valid in the contex of a web-aware Spring ApplicationContext
No declaration is needed for singleton scope.
In Java configuration use the @Scope annotation. We see the xml configuaration above.
Dependency Injection and IoC
Dependency Injection
How objects obtain dependent objects
The class being injected has no responsibillity in instantiating the object being injected Key Theme: Avoid Tight Coupling
Types of Dependency Injection:
- By class properties - not reccommended
- By Setter
- By Constructor - most perfered way
Inversion of Control
A technique to allow dependencies to be injected at runtime
IoC vs DI
- DI is the composition of your classes
- IoC is the runtime enviornment of your code
Spring Configuration and StereoTypes
Spring Configuration Options
- XML Based Configuration
- Introduced in Spring Framework 2.0
- Still supported in Spring 5.x
- Common in legacy applications
- Annotation Based Configuration
- Introduced in Spring 3.0
- Picked up via 'Component Scans'
- Refers to class level annotaions
- @Controller, @Service, @Component, @Repository, etc.
- Java Based Configurations
- Introduced in Spring 3.0
- Uses java classes to define Spring Beans
- Configuration classes are defined with @Configuration
- Beans (components) are defined with @Bean
- Groovy Bean Definition DSL Configuration
- Introduced in Spring 4.0
- Allows you to declare beans in Groovy
- Borrowed from Grails
Use one or a combination of the above. They will work seemlessly together to define beans in the Spring Context The industry tends to favor Java based config
Spring Framework Sterotypes
Spring Sterotypes are used to define Spring beans in the Spring context
Available Stereotypes: @Component, @Controller, @RestController, @Repository, @Service
- Everything inherits from Component
- Repository means it dealing with the data layer
Repository is cool because Spring will detect platform specific persistence exceptions and re-throw them as Spring exceptions
Reactive Programming
Reactive programming focuses on non-blocking, asynchronous execution
The Reactive Manifesto
Responsive
- The System responds in a timely manner
- Responsiveness is the cornerstone of usability and utility
- Reponsiveness also means problems may be detected quickly
- Responsive systems provide rapid and consistent response times
- Consistent behavior simplifies error handling, builds end user confidence, and encourages further interaction
Resilient
- System stays responsive in the face of failure
- Resilience is achieved by replication, containment, isolation, and delegation
- Failures are contianed within each component
- Parts of the system can fail, without compromising the system as a whole
- Recovery of each component is delegated to another
- High-availability is ensured by replication where necessary
Elastic
- The System stays responsive under varying workload
- Reactive Systems can react to changes in the input rate by changing the amount of resources allocated to service inputs
- Reactive Systems achieve elaticity in a cost effective way on commodity hardware and software platforms
Message Driven
- Reactive Systems rely on asynchronous message passing to establish a boundary between components
- This ensures loose coupling, isolation, and location transparency
- Message passing enables load management, elasticity and flow control
- Location transparent messaging makes management of failures possible
- Non-blocking communication allows recipients to only consume resources while active leading to less system overhead
Features of Reactive Programming
- Data Streams
- Asyncronous
- Non-blocking - Process what is availble and ask for a notificaiton when more is available
- Backpressure - The ability of the client to throttle data
- Failures as Messages - Exceptions are processed by a handler function
Reactive Streams
- Reactive Streams API was started in 2013 by engineers from Netflix and other companies. It was officially released 2015 and is now part of Java 9 JDK
- spring-webflux is a whole new non-blocking API for Spring
Spring Reactive Types
- Mono - A publisher with 0 or 1 elements in datastream
- Flux - A publisher with 0+ elements in datastream
Docker
What is Docker?
- Docker is a standard for Linux containers
- A "Container" is an isolated runtime inside of Linux
- A "Container" provides a private machine like space under Linux
- Containers will run under any modern Linux Kernel
Containers can:
- Have their own process space
- Their own network interface
- 'Run' processes as root from inside the container
- Have their own disk space
- Can also share with Host
Docker Terminology
- Docker Image - The representation of a Docker Container.
- Docker Container - The standard runtime of Docker
- Docker Engine - The code which manages Docker stuff
Docker Editions
Docker Community Edition
- CaaS (Contianer as a Service platform subscription)
- Quarterly Releases
- Enterprise Class Support
Docker Enterprise Edition
- Free for developers and operations
- Monthly 'edge' release for devs and quartly releases for operations
Docker uses the release number of year.month.version.edition (yy.mm.v.ed)
Docker Images
- Images are immutable
- Images are built in layers
- Each layer is an immutable file, but is a collection of files and directories
- Layers recive an ID calculated via a SHA 256 hash of the layer contents
- So if layer contents change at all, the SHA 256 hash changes also
- Running docker images gives the first 12 characters of the hash in 'Image ID'
- The hash values of images are referred to by 'tag' names
- The format of the full tag name is: [REGISTRYHOST/][USERNAME/]NAME[:TAG]
- For Registry Host 'registry.hub.docker.com' is inferred
- For ':TAG' - 'latest' is default and inferred
- Full tag example: 'registry.hub.docker.com/mongo:latest'
- Old volumes and containers can pile up and use up disk space, cleanup is important
Data Validation with JSR-303
- Introduced Java Bean Validation
- Part of JEE 6 and above, suported by Spring since version 3
- Set of annotations used to validate Java Bean properties
JSR 380 - Bean Validation 2.0
- Added to Spring Framework 5.0
- Available in Spring Boot 2.0.0+
- Uses Hibnernate Validatior 6.0+
- Primary goal of Bean Validation 2.0 is Java 8 language features
- Added 11 new built in validation annotations
Built in Contstraint Definitions
- @Null - Checks value is null
- @NotNull - Checks value is not null
- @AssertTrue - Value is true
- @AssertFalse - What do you think?
- @Min - Number is equal to or higher
- @Max - Number is equal to or less
- @DecimalMin - Value is larger
- @DecimalMax - Value is less than
- @Negative - Value is less than zero. Zero invalid
- @NegativeOrZero - Value is less than or zero.
- @Positive
- @PositiveOrZero
- @Size - Checks if string or collection is between a min and max
- @Digits - checks for integer digits and fraction digits
- @Past - Checks if a date is in the past
- @PastOrPresent
- @Future
- @FutureOrPresent
- @Pattern - Checks against RegEx pattern
- @NotEmpty - Checks if value is null or empty (whitespace or empty collection)
- @NonBlank - Checks string is not null or not whitespace characters
- @Email - Checks if string value is an email address
Hibernate Validator Constraints
- @ScriptAssert - Class level annotation, checks class against script
- @CreditCardNumber - Verifies value is a credit card number
- @Currency - Valid currency ammount
- @DurationMax - Duration less than given value
- @DurationMin - Duration more than given value
- @EAN - Valid EAN barcode
- @ISBN - Valid ISBN value
- @Length - String length between given min and max
- @CodePointLength - Validates that code point length of the annotated character sequence is between min and max
- @LuhnCheck - Luhn check sum
- @Mod10Check - Mod 10 check sum
- @Mod11Check - Mod 11 check sum
- @Range - Checks if a number is between min and max (exlusive)
- @Safehtml - Checks for safe HTML
- @UniqueElements - Checks if collection has unqiue elements
- @Url - Checks for valid URL
Exception Handling
JSR 303
- Introduced Java Bean Validation
- Part of JEE 6 and above, suported by Spring since version 3
- Set of annotations used to validate Java Bean properties
JSR 380 - Bean Validation 2.0
- Added to Spring Framework 5.0
- Available in Spring Boot 2.0.0+
- Uses Hibnernate Validatior 6.0+
- Primary goal of Bean Validation 2.0 is Java 8 language features
- Added 11 new built in validation annotations
Built in Contstraint Definitions
- @Null - Checks value is null
- @NotNull - Checks value is not null
- @AssertTrue - Value is true
- @AssertFalse - What do you think?
- @Min - Number is equal to or higher
- @Max - Number is equal to or less
- @DecimalMin - Value is larger
- @DecimalMax - Value is less than
- @Negative - Value is less than zero. Zero invalid
- @NegativeOrZero - Value is less than or zero.
- @Positive
- @PositiveOrZero
- @Size - Checks if string or collection is between a min and max
- @Digits - checks for integer digits and fraction digits
- @Past - Checks if a date is in the past
- @PastOrPresent
- @Future
- @FutureOrPresent
- @Pattern - Checks against RegEx pattern
- @NotEmpty - Checks if value is null or empty (whitespace or empty collection)
- @NonBlank - Checks string is not null or not whitespace characters
- @Email - Checks if string value is an email address
Hibernate Validator Constraints
- @ScriptAssert - Class level annotation, checks class against script
- @CreditCardNumber - Verifies value is a credit card number
- @Currency - Valid currency ammount
- @DurationMax - Duration less than given value
- @DurationMin - Duration more than given value
- @EAN - Valid EAN barcode
- @ISBN - Valid ISBN value
- @Length - String length between given min and max
- @CodePointLength - Validates that code point length of the annotated character sequence is between min and max
- @LuhnCheck - Luhn check sum
- @Mod10Check - Mod 10 check sum
- @Mod11Check - Mod 11 check sum
- @Range - Checks if a number is between min and max (exlusive)
- @Safehtml - Checks for safe HTML
- @UniqueElements - Checks if collection has unqiue elements
- @Url - Checks for valid URL
Data Binding in Spring
- Command Objects (aka Backing Beans) - Used to transfer data to and from web forms
- Spring will automatically bind data of form posts
- Binding done by property name (without the 'get' / 'set')
Ex. address.addressLine1 would bind to the addressLine1one of the address property of the PersonBean
Options for Database Intialization
DDL = Data Definition Lang DML = Data Manipulation Lang
Hibernate
- Allows the database to be created from the JPA in memory
- Hibernate property is set by the Spring property
spring.jpa.hibernate.ddl-auto
- Options are:
- none - Hibernate does nothing
- validate - Fail if there is a table or column missing
- update - Automatically update database model by running ddl statements.*
- create - Create tables everytime*
- create-drop - Same as above except the table is dropped on termination*
*Use with cuation, not reccomended for all production systems
- Spring Boot will use create-drop for embedded databases (hsql, h2, derby) or none otherwise
- Data can be loaded from import.sql
- Must be on the root of class path
- Only executed if set to create or create-drop
Spring JDBC
- Spring DataSource initializer via Spring Boot will by default load schema.sql and data.sql from the root of the classpath
- Spring Boot will also load from schema-${platform}
- May conflict with Hibernate's DDL Auto property
- Should use none or validate
JPA
Java Persistance API
Entity Types
- @OneToOne
- One Entity related to one other
- @OneToMany
- One entity is related to many entities (List, Set, Map, SortedSet, SortedMap)
- @ManyToOne
- Inverse of OneToMany
- @ManyToMany
- Many entities are related to many entities
- Each has a List or Set reference to the other
- A join table is used to define relationships
- Unidirectional - mapping is only done one way, one side does not know about relationship
- Bidirectional - both entities know about each other (reccommended by hibernate)
The "Owning Side" of a relationship will hold the foreign key in the database.
Fetch Type
- Lazy Fetch Type - Data is not queried until referenced
- Eager Fetch Type - Data is queried up front
- Hibernate 5 supports the JPA 2.1 Fetch Type Defaults:
- OneToMany - Lazy
- ManyToOne - Eager
- ManyToMany - Lazy
- OneToOne - Eager
Cascade Types
If I delete the parent will the child be deleted as well?
- JPA Cascade Types control how state changes are cascaded from parent objects to child objects
- JPA Cascade Types
- PERSIST - Save operations will cascade to related entities
- MERGE - related entities are merged when the owning entitiy is merged
- REFRESH - related entities are refreshed when the owning entity is refreshed
- REMOVE - removes all related entities when the owning entity is deleted
- DETACH - detaches all related entities if a manual detach occurs
- ALL - Applies all the above cascade options.
By default, no operations are cascaded
Embeddable Types
- JPA/Hibernate support emeddable types
- These are used to define a common set of properties
- For example, a package with a shipping and billing address
Inheritance
- MappedSuperclass - Entities inherit from a super class. A database table IS NOT created for the super class.
- Single Table - (Hibernate Default) - One Table is used for all subclasses
- This can lead to a lot of unused database columns
- Joined Table - Base class and subclasses have their own tables.
- Fetching subclass entities require a join to the table of the superclass. (could cause preformance issues)
- Table Per Class - Each subclass has its own table.
Create and Update Timestamps
- For audit purposes it is often a best practice to use timestamps
- JPA supports @Prepersist and @PreUpdate which can be used to support audit timestamps via JPA lifecycle callbacks.
- Hibernate provides @CreationTimestamp and @UpdateTimestamp
Spring Boot
Spring boot is sort of a configuration wrapper around the Spring MVC Framework.
Dependency Management
Maven or Gradle are supported. Can be used with Ant, but not reccommended.
Each version of Spring Boot is configured to work with a specific version of Spring Framework. Whenever possible, do not specify versions in POM, allow versions to inherit from parent. Don't override the Spring Framework version!!
Spring Boot Starters
Starters are top level dependencies for popular Java libraries. They will bring in deps for the project and related Spring components (Hibernate, JPA, etc.)
Spring Boot Annotations
- @SpringBootApplication - main annotation to use. Includes:
- @Configuration - Delcares class as Spring Configuation
- @EnableAutoConfiguration - Enables auto configuration
- @ComponentScan - Scans for components in current package and all child packages
Disabling Specific Auto Config
Autoconfig brings in A LOT of configuration classes in supplied Spring Boot Jars To Exlude: @EnableAutoConfiguration(exlude={DataSourceAutoConfiguration.class}). Pass in the parameter -debug for a report of the Autoconfig
Dependency Inversion Principle
-
One of the basic rules of good programming is to avoid tight coupling. Ex, creating an object of a class using the new operator results in a class being tightly coupled to another class.
- This does not disrupt small applications, but in enterprise application development this can have serious consequences
-
When one class knows explicitly about the design and implementation of another class, changes to one class raise the risk of breaking another class
-
Dependency Inversion Principle represents the last 'D' of the SOLD principles as published in 1996. The principle states:
- High-level modules should not depend on low-level modules. Both should depend on abstractions.
- Abstractions should not depend on details. Details should depend on abstractions.”
-
Conventional application architecture follows a top-down design approach where a high-level problem is broken into smaller parts. In other words, the high-level design is described in terms of these smaller parts. As a result, high-level modules that gets written directly depends on the smaller (low-level) modules.
-
What Dependency Inversion Principle says is that instead of a high-level module depending on a low-level module, both should depend on an abstraction. Let us look at it in the context of Java through this figure.
-
What the principle has done is:
-
Both Object A and Object B now depends on Interface A, the abstraction.
-
It inverted the dependency that existed from Object A to Object B into Object B being dependent on the abstraction (Interface A).
-
Violation (Bad) Example
Consider the example of an electric switch that turns a light bulb on or off.
LightBulb.java
public class LightBulb {
public void turnOn() {
System.out.println("LightBulb: Bulb turned on...");
}
public void turnOff() {
System.out.println("LightBulb: Bulb turned off...");
}
}
ElectricPowerSwitch.java
public class ElectricPowerSwitch {
public LightBulb lightBulb;
public boolean on;
public ElectricPowerSwitch(LightBulb lightBulb) {
this.lightBulb = lightBulb;
this.on = false;
}
public boolean isOn() {
return this.on;
}
public void press(){
boolean checkOn = isOn();
if (checkOn) {
lightBulb.turnOff();
this.on = false;
} else {
lightBulb.turnOn();
this.on = true;
}
}
}
Our switch is now ready for use to turn on and off the light bulb. But the mistake we did is apparent. Our high-level ElectricPowerSwitch class is directly dependent on the low-level LightBulb class. if you see in the code, the LightBulb class is hardcoded in ElectricPowerSwitch.
Following the Dependency Inversion Principle
To follow the Dependency Inversion Principle in our example, we will need an abstraction that both the ElectricPowerSwitch and LightBulb classes will depend on. But, before creating it, let’s create an interface for switches. Switch.java
package guru.springframework.blog.dependencyinversionprinciple.highlevel;
public interface Switch {
boolean isOn();
void press();
}
We wrote an interface for switches with the isOn() and press() methods. This interface will give us the flexibility to plug in other types of switches, say a remote control switch later on, if required. Next, we will write the abstraction in the form of an interface, which we will call Switchable. Switchable.java
package guru.springframework.blog.dependencyinversionprinciple.highlevel;
public interface Switchable {
void turnOn();
void turnOff();
}
In the example above, we wrote the Switchable interface with the turnOn() and turnoff() methods. From now on, any switchable devices in the application can implement this interface and provide their own functionality. Our ElectricPowerSwitch class will also depend on this interface, as shown below: ElectricPowerSwitch.java
package guru.springframework.blog.dependencyinversionprinciple.highlevel;
public class ElectricPowerSwitch implements Switch {
public Switchable client;
public boolean on;
public ElectricPowerSwitch(Switchable client) {
this.client = client;
this.on = false;
}
public boolean isOn() {
return this.on;
}
public void press(){
boolean checkOn = isOn();
if (checkOn) {
client.turnOff();
this.on = false;
} else {
client.turnOn();
this.on = true;
}
}
}
In the ElectricPowerSwitch class we implemented the Switch interface and referred the Switchable interface instead of any concrete class in a field. We then called the turnOn() and turnoff() methods on the interface, which at run time will get invoked on the object passed to the constructor. Now, we can add low-level switchable classes without worrying about modifying the ElectricPowerSwitch class. We will add two such classes: LightBulb and Fan. LightBulb.java
package guru.springframework.blog.dependencyinversionprinciple.lowlevel;
import guru.springframework.blog.dependencyinversionprinciple.highlevel.Switchable;
public class LightBulb implements Switchable {
@Override
public void turnOn() {
System.out.println("LightBulb: Bulb turned on...");
}
@Override
public void turnOff() {
System.out.println("LightBulb: Bulb turned off...");
}
}
Fan.java
package guru.springframework.blog.dependencyinversionprinciple.lowlevel;
import guru.springframework.blog.dependencyinversionprinciple.highlevel.Switchable;
public class Fan implements Switchable {
@Override
public void turnOn() {
System.out.println("Fan: Fan turned on...");
}
@Override
public void turnOff() {
System.out.println("Fan: Fan turned off...");
}
}
In both the LightBulb and Fan classes that we wrote, we implemented the Switchable interface to provide their own functionality for turning on and off. While writing the classes, if you have missed how we arranged them in packages, notice that we kept the Switchable interface in a different package from the low-level electric device classes. Although, this did not make any difference from coding perspective, except for an import statement, by doing so we have made our intentions clear- We want the low-level classes to depend (inversely) on our abstraction. This will also help us if we later decide to release the high-level package as a public API that other applications can use for their devices.
Summary of the Dependency Inversion Principle
Robert Martin equated the Dependency Inversion Principle, as a first-class combination of the Open Closed Principle and the Liskov Substitution Principle, and found it important enough to give its own name. While using the Dependency Inversion Principle comes with the overhead of writing additional code, the advantages that it provides outweigh the extra effort. Therefore, from now whenever you start writing code, consider the possibility of dependencies breaking your code, and if so, add abstractions to make your code resilient to changes.
Open Closed Principle
-
In good application design and the code writing part, you should avoid change in the existing code when requirements change
-
Instead, you should extend the existing functionality by adding new code to meet the new requirements. You can achieve this by following the Open Closed Principle.
-
The Open Closed Principle represents the “O” of the five SOLID software engineering principles to write well-designed code that are more readable, maintainable, and easier to upgrade and modify.
-
This principle states: “software entities (classes, modules, functions, etc.) should be open for extension, but closed for modification “.
- Open for extension “: This means that the behavior of a software module, say a class can be extended to make it behave in new and different ways.
- note here that the term “extended ” is not limited to inheritance using the Java extend keyword. What it means here is that a module should provide extension points to alter its behavior. One way is to make use of polymorphism to invoke extended behaviors of an object at run time.
- “Closed for modification “: This means that the source code of such a module remains unchanged.
- Open for extension “: This means that the behavior of a software module, say a class can be extended to make it behave in new and different ways.
Violation (Bad) Example
HealthInsuranceSurveyor.java
package guru.springframework.blog.openclosedprinciple;
public class HealthInsuranceSurveyor{
public boolean isValidClaim(){
System.out.println("HealthInsuranceSurveyor: Validating health insurance claim...");
/*Logic to validate health insurance claims*/
return true;
}
}
ClaimApprovalManager.java
package guru.springframework.blog.openclosedprinciple;
public class ClaimApprovalManager {
public void processHealthClaim (HealthInsuranceSurveyor surveyor) {
if(surveyor.isValidClaim()){
System.out.println("ClaimApprovalManager: Valid claim. Currently processing claim for approval....");
}
}
}
The above follows the single responsibillity principle, but when a new type of insurance claim in introduced the code has to be modified: : Modified ClaimApprovalManager.java
package guru.springframework.blog.openclosedprinciple;
public class ClaimApprovalManager {
public void processHealthClaim (HealthInsuranceSurveyor surveyor) {
if(surveyor.isValidClaim()){
System.out.println("ClaimApprovalManager: Valid claim. Currently processing claim for approval....");
}
}
public void processVehicleClaim (VehicleInsuranceSurveyor surveyor) {
if(surveyor.isValidClaim()){
System.out.println("ClaimApprovalManager: Valid claim. Currently processing claim for approval....");
}
}
}
Coding to the Open Closed Principal
- Open to support more types of claims
- Closed for any modifications when support for a new type of claim is added
InsuranceSurveyor.java
package guru.springframework.blog.openclosedprinciple;
public abstract class InsuranceSurveyor {
public abstract boolean isValidClaim();
}
HealthInsuranceSurveyor.java
package guru.springframework.blog.openclosedprinciple;
public class HealthInsuranceSurveyor extends InsuranceSurveyor{
public boolean isValidClaim(){
System.out.println("HealthInsuranceSurveyor: Validating health insurance claim...");
/*Logic to validate health insurance claims*/
return true;
}
}
VehicleInsuranceSurveyor.java
package guru.springframework.blog.openclosedprinciple;
public class VehicleInsuranceSurveyor extends InsuranceSurveyor{
public boolean isValidClaim(){
System.out.println("VehicleInsuranceSurveyor: Validating vehicle insurance claim...");
/*Logic to validate vehicle insurance claims*/
return true;
}
}
ClaimApprovalManager.java
package guru.springframework.blog.openclosedprinciple;
public class ClaimApprovalManager {
public void processClaim(InsuranceSurveyor surveyor){
if(surveyor.isValidClaim()){
System.out.println("ClaimApprovalManager: Valid claim. Currently processing claim for approval....");
}
}
}
Our insurance system is now open to support more types of insurance claims, and closed for any modifications whenever a new claim type is added.
Summary
Most of the times real closure of a software entity is practically not possible because there is always a chance that a change will violate the closure. For example, in our insurance example a change in the business rule to process a specific type of claim will require modifying the ClaimApprovalManager class. So, during enterprise application development, even if you might not always manage to write code that satisfies the Open Closed Principle in every aspect, taking the steps towards it will be beneficial as the application evolves.
Interface Segregation Principle
- Interfaces are a core part of Java and are used extensively to achieve abstraction and to support multiple inheritance of type (the ability of a class to implement more than one interface)
- This principle states that “Clients should not be forced to depend on methods that they do not use”. Here, the term “Clients” refers to the implementing classes of an interface.
- Basically, your interface shouldn't be bloated with methods that implementing classes don't require
- "Fat Interfaces" implement classes that are unnecessarily forced to provide implementations (dummy/empty) even for those methods that they don't need
- In addition, the classes are subject to change when the interface changes. An addition of a method or change to a method signature requires modifying all the implementation classes even if some of them don't use the method
- Break "Fat interfaces" into smaller and highly cohesive interfaces known as "role interfaces"
- Each "role interface declares one or more methods for a speicific behavior,"
Violation (Bad) Example
Consider the requirements of an application that builds different types of toys. Each toy will have a price and color. Some toys, such as a toy car or toy train can additionally move, while some toys, such as a toy plane can both move and fly. An interface to define the behaviors of toys is this.
Toy.java
public interface Toy {
void setPrice(double price);
void setColor(String color);
void move();
void fly();
}
ToyHouse.java
public class ToyHouse implements Toy {
double price;
String color;
@Override
public void setPrice(double price) {
this.price = price;
}
@Override
public void setColor(String color) {
this.color=color;
}
@Override
public void move(){}
@Override
public void fly(){}
}
As you can see, it is useless to provide ToyHouse with implementations of move and fly. This also leads to violation of the open closed principle
Following the Interface Segregation Principle
Create interfaces for specific behaviors Toy.java
package guru.springframework.blog.interfacesegregationprinciple;
public interface Toy {
void setPrice(double price);
void setColor(String color);
}
Movable.java
package guru.springframework.blog.interfacesegregationprinciple;
public interface Movable {
void move();
}
Flyable.java
package guru.springframework.blog.interfacesegregationprinciple;
public interface Flyable {
void fly();
}
Now we can implement classes which only implement interfaces they are interested in.
ToyHouse.java
package guru.springframework.blog.interfacesegregationprinciple;
public class ToyHouse implements Toy {
double price;
String color;
@Override
public void setPrice(double price) {
this.price = price;
}
@Override
public void setColor(String color) {
this.color=color;
}
@Override
public String toString(){
return "ToyHouse: Toy house- Price: "+price+" Color: "+color;
}
}
ToyCar.java
package guru.springframework.blog.interfacesegregationprinciple;
public class ToyCar implements Toy, Movable {
double price;
String color;
@Override
public void setPrice(double price) {
this.price = price;
}
@Override
public void setColor(String color) {
this.color=color;
}
@Override
public void move(){
System.out.println("ToyCar: Start moving car.");
}
@Override
public String toString(){
return "ToyCar: Moveable Toy car- Price: "+price+" Color: "+color;
}
}
ToyPlane.java
package guru.springframework.blog.interfacesegregationprinciple;
public class ToyPlane implements Toy, Movable, Flyable {
double price;
String color;
@Override
public void setPrice(double price) {
this.price = price;
}
@Override
public void setColor(String color) {
this.color=color;
}
@Override
public void move(){
System.out.println("ToyPlane: Start moving plane.");
}
@Override
public void fly(){
System.out.println("ToyPlane: Start flying plane.");
}
@Override
public String toString(){
return "ToyPlane: Moveable and flyable toy plane- Price: "+price+" Color: "+color;
}
}
Summary of Interface Segregation Principle
Both the Interface Segregation Principle and Single Responsibility Principle have the same goal: ensuring small, focused, and highly cohesive software components. The difference is that Single Responsibility Principle is concerned with classes, while Interface Segregation Principle is concerned with interfaces.Interface Segregation Principle is easy to understand and simple to follow. But, identifying the distinct interfaces can sometimes be a challenge as careful considerations are required to avoid proliferation of interfaces. Therefore, while writing an interface, consider the possibility of implementation classes having different sets of behaviors, and if so, segregate the interface into multiple interfaces, each having a specific role.
Interface Segregation Principle in the Spring Framework
The Interface Segregation Principle becomes especially important when doing Enterprise Application Development with the Spring Framework.
As the size and scope of the application you’re building grows, you are going to need pluggable components. Even when just for unit testing your classes, the Interface Segregation Principle has a role. If you’re testing a class which you’ve written for dependency injection it is ideal that you write to an interface. By designing your classes to use dependency injection against an interface, any class implementing the specified interface can be injected into your class. In testing your classes, you may wish to inject a mock object to fulfill the needs of your unit test. But when the class you wrote is running in production, the Spring Framework would inject the real full featured implementation of the interface into your class.
The Interface Segregation Principle and Dependency Injection are two very powerful concepts to master when developing enterprise class applications using the Spring Framework.