Thursday, October 3, 2024

SOLID principles in java

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..


package SOLID.aSingle_responsibility;

/*
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
       =((pen.price*this.quantity));
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

package SOLID.bopen_closed;

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.

package SOLID.cLiskov;

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

package SOLID.dinterfaceSegregation;


/*
“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 .

package SOLID.eDependencyInversion;
//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
type of mouse/keyboard
//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);
}
}




Monday, December 27, 2021

Java 8

 Lambdas    

it is an anonymous function. without return type and without modifiers.

the objective of a lambda expression is 
1)to bring functional programming benefits in java.
2) write more readable, maintainable & concise code
3) to use API very easily and effectively
4) enable parallel processing 

eg1:
public void m1()
{
System.out.println("Hello");
}

can be written as 
()-> System.out.println("Hello");

eg2: 
public void add(int a,int b)
{
System.out.println(a+b);
}

can be written as 
(int a, int b)-> System.out.println(a+b);
also
(a,b)->  System.out.println(a+b);

eg3:
 public int square(int n) 
{
return n*n;
}

can be written as 
(int n)->{return n*n;}
also
(n)->return n*n;
also
n->n*n;

eg4:
public int m1(String s)
{
return s.length();
}

can be written as 

s->s.length();

Functional interfaces

Any interface with only one abstract method is known as a functional interface.

case 1: if a parent interface is functional interface and child interface is marker 
@FunctionalInterface
interface P
{
public void m1();
}
@FunctionalInterface
interface C extends P
{
}
the scenario is valid

case 2:
@FunctionalInterface
interface P
{
public void m1();
}
@FunctionalInterface
interface C extends P
{
public void m1();
}
valid scenario

case 3:
@FunctionalInterface
interface P
{
public void m1();
}
@FunctionalInterface
interface C extends P
{
public void m2();
}
Error: java: Unexpected @FunctionalInterface annotation
  LambdaFeatures.C is not a functional interface
    multiple non-overriding abstract methods found in interface LambdaFeatures.C

Invoking Lambda Expression By using Functional Interface example-1

@FunctionalInterface
interface Intref{
public void m1();
}
class A implements Intref{
@Override
public void m1() {
System.out.println("normal implementation");
}
}
public class FIPract1 {
public static void main(String[] args) {
Intref i1=new A();
i1.m1();
Intref i2= ()->System.out.println("implemented Lambda");
i2.m1();
}
}
O/P:
 normal implementation
implemented Lambda

Invoking Lambda Expression By using Functional Interface example-2

public class FIPract2 {
public static void main(String[] args) {
Interf i1=new Demo();
i1.add(2,3);
Interf i2= (a,b) -> System.out.println("sum is "+(a+b));
i2.add(3,4);
}
}
interface Interf
{
public void add(int a,int b);
}
class Demo implements Interf{
@Override
public void add(int a, int b) {
System.out.println("sum is "+(a+b));
}
}
O/P:
sum is 5
sum is 7



Invoking Lambda Expression By using Functional Interface example-2
public class FIPract2 {
public static void main(String[] args) {
Interf i1=new Demo();
System.out.println("length is "+i1.getLength("Hello"));
Interf i2=s -> s.length();
System.out.println(i2.getLength("haiii"));
Interf i3=(String::length);
System.out.println(i3.getLength("hai"));
}
}
interface Interf
{
public int getLength(String s);
}
class Demo implements Interf{
@Override
public int getLength(String s) {
return s.length();
}
}
O/P: 
length is 5
5
3


public static void main(String[] args) {
List<Integer> l=new ArrayList<Integer>();
l.add(21);
l.add(5);
l.add(68);
l.add(45);
System.out.println("before sorting"+l);
Collections.sort(l,(a,b) -> (a>b)?-1:(a<b)?1:0);
System.out.println("after sorting"+l);

Set<Integer> set=new TreeSet<Integer>((a,b) -> (a>b)?-1:(a<b)?1:0);
set.add(35);
set.add(22);
set.add(12);
set.add(45);
System.out.println("Set is"+set);

Map<Integer,String> map=new TreeMap<Integer,String>((a,b) -> (a>b)?-1:(a<b)?1:0);
map.put(6,"abc");
map.put(600,"abc");
map.put(340,"abc");
map.put(650,"abc");
System.out.println("map is "+map);
}
O/P: 
before sorting[21, 5, 68, 45]
after sorting[68, 45, 21, 5]
Set is[45, 35, 22, 12]
map is {650=abc, 600=abc, 340=abc, 6=abc}

the method level local variable used in Lambda expression must be final or effectively final.

public class FIPract4 {
int x=10;
public void m2(){
int y=20;
Inte i=()->{
System.out.println(x);
System.out.println(y);
x=999;
y=888; //the local variables inside lambda are implicitly final
};
i.m1();
}

public static void main(String[] args) {
FIPract4 p=new FIPract4();
p.m2();
}
}

interface Inte {
public void m1();
}
OUTPUT:
Error:(12, 28) java: local variables referenced from a lambda expression must be final or effectively final

Default methods inside the interface

eg:
public class DefaultInter implements DefInt{
public void m1(){
System.out.println("own implementation");
}
public static void main(String[] args) {
DefaultInter d=new DefaultInter();
d.m1();
}
}
interface DefInt
{
default void m1(){
System.out.println("default");
}
}
Output: own implementation

default method with same name in parralel class

public class DefaultInter2 implements Left,Right {
public static void main(String[] args) {
DefaultInter2 d=new DefaultInter2();
}
}
interface Left{
default void m1(){
System.out.println("left interface");
}
}
interface Right{
default void m1(){
System.out.println("right interface");
}
}
O/P: Error:java: class LambdaFeatures.DefaultInter2 inherits unrelated defaults for m1() from types LambdaFeatures.Left and LambdaFeatures.Right


solution is to mandatorily implement methods 
public class DefaultInter2 implements Left,Right {
public static void main(String[] args) {
DefaultInter2 d=new DefaultInter2();
d.m1();
}
@Override
public void m1() {
System.out.println("own implementation");
Left.super.m1();
Right.super.m1();
}
}
interface Left{
default void m1(){
System.out.println("left interface");
}
}
interface Right{
default void m1(){
System.out.println("right interface");
}
O/P: own implementation
left interface
right interface

Method reference & constructor reference by :: OPERATOR

for static method class name would be enough for reference but when it comes to non-static methods there needs object reference for the same. the only requirement is the same argument type
public class MethodReference {
public static void main(String[] args) {
MethodReference m=new MethodReference();
Runnable r= m::m1;
Runnable r2=MethodReference::m2;
Thread t=new Thread(r);
Thread t1=new Thread(r2);
t.start();
t1.start();
for (int i=0;i<10;i++){
System.out.println("main thread");
}
}
public void m1(){
for (int i=0;i<10;i++){
System.out.println("child thread");
}
}
public static void m2(){
for (int i=0;i<10;i++){
System.out.println("child thread 2");
}
}
}

Constructor reference

public class ConstReference {
public static void main(String[] args) {
Int4 i=Sample::new;
i.get();
}
}
class Sample{
Sample(){
System.out.println("created sample object");
}
}
interface Int4{
Sample get();
}
O/P:created sample object

Streams

Streams is useful when we want to process objects from Collection .

public class StreamPract1 {
public static void main(String[] args) {
ArrayList<Integer> l=new ArrayList<Integer>();
l.add(0);
l.add(13);
l.add(20);
l.add(25);
System.out.println(l);
//without stream
for (Integer i1: l){
if (i1%2==0) System.out.println(i1);
}
//with stream
List<Integer> l1= l.stream().filter(i->i%2==0).collect(Collectors.toList());
System.out.println(l1);
//without stream
for (Integer i1: l) System.out.println(i1*2);
//with stream
System.out.println(l.stream().map(i->i*2).collect(Collectors.toList()));
}
}
O/P: 
[0, 13, 20, 25]
0
20
[0, 20]
0
26
40
50
[0, 26, 40, 50]

Processing by collect() method

This method collects the element from the Stream and adds it to the specified collection.
eg:
map(i->i*2).collect(Collectors.toList()));

Processing by count() method

the method returns the number of elements present in the stream.
eg:
public static void main(String[] args) {
ArrayList<Integer> l=new ArrayList<Integer>();
l.add(0);
l.add(13);
l.add(20);
l.add(25);
l.add(33);
long count=l.stream().filter(i->i%2!=0).count();
System.out.println("odd number is"+count);
}
O/P: odd numbers is 3

Processing by sorted() method

we can use the sorted() method to sort elements inside Stream. we can sort either based on default natural sorting order or customized sorting order.
sorted() or sorted(Comparator c)

public class StreamPract2 {
public static void main(String[] args) {
ArrayList<Integer> l=new ArrayList<Integer>();
l.add(20);
l.add(33);
l.add(25);
l.add(0);
l.add(13);
System.out.println(l.stream().sorted().collect(Collectors.toList()));
System.out.println(l.stream().sorted((a,b)->-a.compareTo(b)).collect(Collectors.toList()));
}
}
O/P: 
[0, 13, 20, 25, 33]
[33, 25, 20, 13, 0]

Processing by min() & max() method

public static void main(String[] args) {
ArrayList<Integer> l=new ArrayList<Integer>();
l.add(20);
l.add(33);
l.add(25);
l.add(13);
l.add(4);
Integer min=l.stream().min((a,b)->a.compareTo(b)).get();
Integer max=l.stream().max((a,b)->a.compareTo(b)).get();
System.out.println("minimum is "+min);
System.out.println("maximum is "+max);
}
O/P:
minimum is 4
maximum is 33

Processing using forEach() method

this method won't return anything, it just takes Lambda expression as argument and apply that Lambda expression for each element present in Stream.
ArrayList<Integer> l=new ArrayList<Integer>();
l.add(20);
l.add(33);
l.add(25);
l.add(13);
l.add(4);
l.stream().forEach(System.out::println);
O/P:
20
33
25
13
4

Processing using toArray() method

Integer[] array=l.stream().toArray(Integer[]::new);

Stream.of()

DATE-TIME API (Joda time API)

public class DaTimePract {
public static void main(String[] args) {
LocalDate date=LocalDate.now();
//System.out.println(date);
int dd=date.getDayOfMonth();
int mm=date.getMonthValue();
int yy=date.getYear();
System.out.printf("%d-%d-%d\n",dd,mm,yy);
LocalTime time=LocalTime.now();
//System.out.println(time);
int h=time.getHour();
int m=time.getMinute();
int s=time.getSecond();
int n=time.getNano();
System.out.printf("%d;%d;%d;%d\n",h,m,s,n);
LocalDateTime dt=LocalDateTime.of(1996,10,15,16,21);
System.out.println(dt);
LocalDate bday=LocalDate.of(1996,10,15);
System.out.println(dt.plusMonths(3));
Period p=Period.between(bday,date);
System.out.println("age is "+p);
int ye=2100;
Year year=Year.of(ye);
if (year.isLeap()){
System.out.println("leap year "+year);
}
else System.out.println("not leap "+year);
ZoneId z=ZoneId.systemDefault();
System.out.println(z);
ZoneId z1=ZoneId.of("America/Los_Angeles");
ZonedDateTime zdt=ZonedDateTime.now(z1);
System.out.println(zdt);
}

SOLID principles in java

SOLID  Single responsibility principle : a class should have only  reason to change, i.e. a class should have only one responsibility/single...