SOLID
Single responsibility principle: a class should have only reason to change, i.e. a class should have only one responsibility/single reason/single purpose.
e.g. A bakers role is of baking bread/ensuring the quality but not other tasks such as maintaining the inventory, serving ,cleaning etc..
/*
This principle states that “A class should have only one reason to change”
which means every class should have a single responsibility or single job or single purpose.
In other words, a class should have only one job or purpose within the software system.
*/
public class Invoice {
private Pen pen;
private int quantity;
Invoice(Pen pen,int quantity){
this.pen=pen;
this.quantity=quantity;
}
public int calculateTotal(){
int total
return total;
}
public void printInvoice(){
//print method
// this method is not supposed to be here as per SOLID principle hence
//please refere class Invoice Printer at line number 27
}
public void saveToDB(){
// this method is not supposed to be here as per SOLID principle hence
//please refer class InvoiceDao at line number 36
}
}
class InvoicePrinter{
Invoice invoice;
public InvoicePrinter(Invoice invoice)
this.invoice=invoice;
}
public void printInvoice(){
//print method actual
}
}
class InvoiceDao{
Invoice invoice;
public InvoiceDao(Invoice invoice){
this.invoice=invoice;
}
public void saveToDB(){
//save method actual
System.out.println("saved finally ");
}
}
class Pen{
static int price=100;
}
Open-Closed Principle: a software entity(class, method etc.) must be open for extension but closed for any modifications
import java.security.PublicKey;
import java.util.List;
import java.util.stream.Stream;
/*
Open/Closed Principle
This principle states that
“Software entities (classes, modules, functions, etc.) should be open for extension, but closed for modification”
which means you should be able to extend a class behavior, without modifying it.
*/
public class Product {
public String name;
public Color color;
public Size size;
public Product(String name,Color color,Size size){
this.name=name;
this.color=color;
this.size=size;
}
}
enum Color{
RED,GREEN,BLUE;
}
enum Size {
SMALL,MEDIUM,LARGE,YUGE;
}
class ProductFilter{
public Stream<Product> filterByColor(List<Product> products, Color color){
return products.stream().filter(p->p.color==color);
}
//we are violating open-closed principle by creating new filters
public Stream<Product> filterBySize(List<Product> products, Size size){
return products.stream().filter(p->p.size==size);
}
public Stream<Product> filterByColorandSize(List<Product> products, Color color,Size size){
return products.stream().filter(p->p.color==color && p.size==size);
}
}
class Demo{
public static void main(String[] args) {
Product pen=new Product("Pen",Color.BLUE,Size.SMALL);
Product apple=new Product("Apple",Color.GREEN,Size.MEDIUM);
Product house=new Product("Home",Color.RED,Size.LARGE);
List<Product> plist=List.of(apple,pen,house);
ProductFilter pf=new ProductFilter();
pf.filterByColor(plist,Color.RED).forEach(p->System.out.println(p.name+" "+"is "+p.color));
BetterFilter bf=new BetterFilter();
bf.filter(plist,new ColorSpecification(Color.BLUE)).forEach(p->System.out.println(p.name+" "+"is "+p.color));
bf.filter(plist,new SizeSpecification(Size.LARGE)).forEach(p->System.out.println(p.name+" "+"is "+p.size));
System.out.println("large and red items combination");
bf.filter(plist,
new AndSpecification<>(
new ColorSpecification(Color.RED),
new SizeSpecification(Size.LARGE)
)).forEach(p-> System.out.println("--"+p.name+" + "+p.color));
}
}
// creating interfaces to follow open-closed principle
interface Specification<T>{
boolean isSatisfies(T item);
}
interface Filter<T>{
Stream<T> filter(List<T> items,Specification<T> spec);
}
class ColorSpecification implements Specification<Product>{
private Color color;
public ColorSpecification(Color color){
this.color=color;
}
@Override
public boolean isSatisfies(Product p) {
return p.color==color;
}
}
class SizeSpecification implements Specification<Product>{
private Size size;
public SizeSpecification(Size size){
this.size=size;
}
@Override
public boolean isSatisfies(Product p) {
return p.size== size;
}
}
class BetterFilter implements Filter<Product>{
@Override
public Stream<Product> filter(List<Product> items, Specification<Product> spec) {
return items.stream().filter(p->spec.isSatisfies(p));
}
}
class AndSpecification<T> implements Specification<T>
{
private Specification<T> first, second;
public AndSpecification(Specification<T> first, Specification<T> second) {
this.first = first;
this.second = second;
}
@Override
public boolean isSatisfies(T item) {
return first.isSatisfies(item) && second.isSatisfies(item);
}
}
3) Liskov substitution principle: Child/Derived class should be substitute for the parent/base class and should not alter the behavior/characteristics of the program.
eg: square is a rectangle which is hence a child class of rectangle i.e square consists all functionalities of a rectangle but it wont hamper any program functionalities.
class Rectangle{
protected int widht, height;
public Rectangle(){}
public Rectangle(int widht, int height) {
this.widht = widht;
this.height = height;
}
public int getWidht() {
return widht;
}
public void setWidht(int widht) {
this.widht = widht;
}
public int getHeight() {
return height;
}
public void setHeight(int height) {
this.height = height;
}
public int getArea(){
return this.widht*this.height;
}
@Override
public String toString() {
return "Rectangle{" +
"widht=" + widht +
", height=" + height +
'}';
}
}
class Square extends Rectangle{
public Square(){}
public Square(int size) {
widht=height=size;
}
@Override
public void setWidht(int width) {
super.setWidht(width);
super.setHeight(width);
}
@Override
public void setHeight(int height) {
super.setHeight(height);
super.setWidht(height);
}
}
class RectangleFactory{
public static Rectangle newRectangle(int width, int height){
return new Rectangle(width,height);
}
public static Rectangle newswuare(int side){
return new Rectangle(side, side);
}
}
public class Demo {
static void useIt(Rectangle r){
int width=r.getWidht();
r.setHeight(10);
System.out.println("expect an area of "+ width*10);
System.out.println("but getting "+r.getArea());
}
public static void main(String[] args) {
Rectangle r=new Rectangle(2,3);
useIt(r);
Rectangle s=new Square();
s.setWidht(20);
useIt(s);
}
}
4)Interface Segregation principle: do not force any client to implement an interface that is irrelevant to them.
e.g.: imagine there is an interface multifunctional printer which has functionality such as print. scan, fax etc.. there is a possibility of existing an user who will have only the requirement of print , in that case the other requirement such as fax, scan will be wasted
/*
“do not force any client to implement an interface which is irrelevant to them“.
*/
class Document{
}
interface Machine{
void print(Document d);
void fax(Document d);
void scan(Document d) throws Exception;
}
class MultiFunctionPrinter implements Machine{
@Override
public void print(Document d) {
//printing
}
@Override
public void fax(Document d) {
//for fax
}
@Override
public void scan(Document d) {
//for scanning
}
}
class OldFashionedPrinter implements Machine{
@Override
public void print(Document d) {
//printing
}
//fax to be implemented even though its not in use
@Override
public void fax(Document d) {
}
//scan method too to be implemented even though its not needed
@Override
public void scan(Document d) throws Exception {
throw new Exception();
}
}
//instead we create seperate interfaces with single functionality like single-responsibility
interface Printer{
void print(Document document);
}
interface Scanner{
void scan(Document d);
}
interface Fax{
void fax(Document d);
}
class JustAPrinter implements Printer{
@Override
public void print(Document document) {
//only print here
}
}
class PhotoCopier implements Printer,Scanner{
@Override
public void print(Document document) {
//print method
}
@Override
public void scan(Document d) {
//scan method
}
}
public class Demo {
}
interface pollution: adding methods to an interface even if they are not required by clients is known as interface pollution.
Many small interfaces are better than few bloated interfaces.
Don't use interfaces if you're not going to need all their methods.
Should never have to use UnsupportedOperationException and never return null(that's the worst).
And the golden rule - Optimize early.
5) Dependency inversion principle: High level modules should not depend on low level modules, Both should depend on abstraction.
class should depend on interfaces than concrete classes.
e.g.: we depend on version control system (abstract) git and are not concerned of how it works internally.
if we know git we can use github/gitlab /any version control .
//class should depend on interfaces than on concrete classes
public class MacBook {
private final Keyboard keyboard;
private final Mouse mouse;
//by keeping it abstract we can have it initialized with any
//may it be wired or wireless or any combination
public MacBook(Keyboard keyboard, Mouse mouse) {
this.keyboard = keyboard;
this.mouse = mouse;
}
}
interface Keyboard{}
interface Mouse{}
class WiredKeyboard implements Keyboard{
}
class WiredMouse implements Mouse{
}
class Test{
public static void main(String[] args) {
WiredKeyboard wk=new WiredKeyboard();
WiredMouse wm=new WiredMouse();
MacBook m1=new MacBook(wk,wm);
}
}