开闭原则
定义:一个软件实体如类、模块和函数应该对扩展开放,对修改关闭 ,优点:提高软件系统的可复用性及可维护性
用抽象构建框架,用实现扩展细节 实现开闭原则的思想就是面向抽象编程.而不是面向具体的实现变成,因为抽象呢相对来说是稳定的,让类去依赖于固定的抽象,所以呢,对于修改来说就是封闭的,而通过面向对象的继承以及多态的机制。那就可以实现对抽象体的继承了,那通过重写啊,改变其固有方法,或者实现新的扩展方法
先写一个价格的接口类 :
/**
* 价格抽象接口
*/
public interface ICourse {
Integer getId();
String getName();
Double getPrice();
}
在来写一个具体的实现类
**
* 描述:
* java课程的实现类
*
* @author HeGaoJian
* @version 1.0
* @create 2019-12-16 13:30
*/
public class JavaCourse implements ICourse {
private Integer id;
private String name;
private Double price;
public JavaCourse(Integer id, String name, Double price) {
this.id = id;
this.name = name;
this.price = price;
}
@Override
public Integer getId() {
return this.id;
}
@Override
public String getName() {
return this.name;
}
@Override
public Double getPrice() {
return this.price;
}
}
测试方法 :
public class Test1 {
public static void main(String[] args) {
ICourse course = new JavaCourse(12, "测试课程", (double) 100);
}
}
电商平台中遇到一些活动的时候需要打折,打折的时候需要显示原价的价格和现价的价格 那么我们应该怎么写呢?
方法一 : 我们可以在接口里面直接写一个getOriginPrice的接口方法获取到他的旧的价格,但是接口一修改,对应的类也会修改,那要修改的东西很多,
方法二 继承javaCourse这个方法,在里面单独的写个方法
public class JavaDicCourse extends JavaCourse {
public JavaDicCourse(Integer id, String name, Double price) {
super(id, name, price);
}
public Double getOrPrice() {
return super.getPrice();
}
@Override
public Double getPrice() {
return super.getPrice() * 0.8;
}
}
测试类
public static void main(String[] args) {
ICourse course = new JavaDicCourse(12, "测试课程", (double) 100);
JavaDicCourse dicCourse = (JavaDicCourse) course;
}
那我们增加了一个子类,我们修改的是比较偏应用级的代码,也就是说在应用层的,而底层的接口和底层的基类,我们并没有修改.这样呢,也防止了风险的一个扩散,也就是说。如果我们修改这个接口里面有很多方法,然后在实现类的里边逻辑又非常复杂,
那如果我们改折后价的话,有可能修改到这里边的实现,甚至呢其他实现,在开发过程中都是容易引起bug的。而我们通过继承了一个基类简单的一个方式,使我们对于这个扩展呢是开放的,而对修改这个接口和这个基类呢,是关闭的那我们变化的呢都是应用层的。
我们在面向对象编程的时候啊,我们一定要强调开闭原则,那其他几个原则呢,也都是开闭原则的一个具体形态,首先它提高了我们的复用性,而且提高了可维护。
这就是简单的一个开闭原则的例子 .
依赖倒置原则
定义:高层模块不应该依赖低层模块,二者都应该依赖其抽象,抽象不应该依赖细节;细节应该依赖抽象定义的补充,针对接口编程,不要针对实现编程
优点:可以减少类间的耦合性、提高系统稳定性,提高代码可读性
和可维护性,可降低修改程序所造成的风险 ,那我们在使用依赖倒置原则的时候,还有一些要注意的点,比如说每个类尽量都继承着接口或抽象类。
一个简单的例子 Geely 在学习
public class Geely {
public void studyJavaCourse(){
System.out.println("Geely在学习java");
}
public void studyFEJavaCourse(){
System.out.println("Geely在学习FEJavaCourse");
}
public void studyPyCourse(){
System.out.println("Geely在学习Py");
}
}
test类
public class Test {
//v1
public static void main(String[] args) {
Geely geely = new Geely();
geely.studyJavaCourse();
geely.studyFEJavaCourse();
}
}
我们在应用层直接调用它学习的方法
现在我们的做法就是面向实现编程,因为整个就是一个实现类,我们在面向实现变成会发现一个 重要的问题,这个实现类是要经常修改的,扩展性比较差,也就是说我们应用层的这个函数,这里面的修改是依赖于底层实现的,因为我们没有抽象
造成我们的应用层的这个类及函数是依赖于这个类的,那因为test这个类是应用层的,它属于高层模块呢,而类是属于低层模块。那根据依赖倒置的原则,高层次的模块是不应该依赖于低层次的模块的,也就是说test里边的实现现在依赖于基类的具体实现,里边实现什么我都要来这里边扩展来进行补充。然后在高层模块呢才可以使用.
修改之后 新建一个抽象类
public interface ICourse {
void studyCourse();
}
public class Geely {
//这里Geely去学习实现一个借口
public void studysSourse(ICourse iCourse) {
iCourse.studyCourse();
}
}
让后各个接口的具体实现
public class JavaCourse implements ICourse {
@Override
public void studyCourse() {
System.out.println("Geely在学习Java课程");
}
}
......
应用层的类
public static void main(String[] args) {
Geely geely = new Geely();
geely.studysSourse(new JavaCourse());
geely.studysSourse(new FECourse());
geely.studysSourse(new PythonCourse());
}
Geely 这个类是不需要动的,也就是说我们要面向这个接口编程,我去写的这个扩展类是面向接口的,而不是面向具体的Geely这个实现类,而对于高层模块,具体我们学习什么课程例如PE。我们把他交给高层模块来选择,这样的话呢就坐到了他们俩之间的事解耦.
同时呢Geely 和具体的课程实现是解耦的,它和学习接口是耦合的,那所谓的高内聚低耦合. 上面我们那个是接口注入的方式 当然也可以通过构造器的方式来写这个类
public class Geely {
public ICourse iCourse;
public Geely(ICourse iCourse) {
this.iCourse = iCourse;
}
public void studysSourse() {
iCourse.studyCourse();
}
}
//v3 接口注入的方式 ICourse在构造的时候就已经注入了
public static void main(String[] args) {
Geely geely = new Geely(new JavaCourse());
geely.studysSourse();
}
但是当Geely要去学Python的时候我们还得去new 一个Geely这个对象 ,所以 我们写一个 set 方法,这样Geely这个人就可以愉快的学习多门课程啦!!!
public class Geely {
public ICourse iCourse;
public Geely() {
}
public void setiCourse(ICourse iCourse) {
this.iCourse = iCourse;
}
public Geely(ICourse iCourse) {
this.iCourse = iCourse;
}
public void studysSourse() {
iCourse.studyCourse();
}
}
TEST类
public static void main(String[] args) {
Geely geely = new Geely();
geely.setiCourse(new JavaCourse());
geely.studysSourse();
geely.setiCourse(new FECourse());
geely.studysSourse();
}
依赖倒置的原则就是高层次的模块不应该依赖于低层次的模块
那依赖倒置原则还能表达出一个什么样的事实呢?也就是说相对于细节的多变。抽象的东西要稳定的多,以抽象为基础搭建起来的架构呢,比以细节为基础搭建起来的架构要稳定的多,那我们抽象的目的呢,其实就是制定好规范和契约,比如说Iourse。这个接口学习课程这是一个契约,具体怎么实现交给具体的实现类,而高层模块Geely是不依赖于具体的课程的实现,因为现在呢是这几个种类,说不定呢,过一阵又出现什么新的语言,那Geely你这个类不用动,其他课程的实现也不用动,只需要新增实现就可以了,所以核心思想就是面向接口编程。
单一职责原则
定义:不要存在多于一个导致类变更的原因
一个类/接口/方法只负责一项职责
优点:降低类的复杂度、提高类的可读性,
是高系统的可维护性、降低变更引起的风险,也就是说规定一个类应该只有一个发生变化的原因,这个呢是在类层次上的。他在接口层次还有方法层次上也都要遵循三原则,首先是降低类的复杂性,提高可读性提高。维护性最重要的是变更时风险率降低.
首先我们来看类的单一职能方法 Demo :
public class Bird {
public void mainMoveMode(String birdName){
if("鸵鸟".equals(birdName)){
System.out.println(birdName+"用脚走");
}else{
System.out.println(birdName+"用翅膀飞");
}
}
}
这有一个鸟类,里面有两个方法,当应用层调用的时候是这样的
Bird bird = new Bird();
bird.mainMoveMode("大雁");
bird.mainMoveMode("鸵鸟");
在这里边我们做了一个些判断,这个就非常符合我们日常开发,对于一个需求来说,我在这里边修改其实是最快的方式,那我们在实际工作中啊还要考虑开发的成本,时间,进度等等,那完全遵守单原则,有时候还是真的要看实际情况的,但是我们有一颗按原则写代码的心.在条件允许的情况下还是希望大家来遵守这些设计原则,那需求现在增加了鸵鸟,那我们在原有的类加一个判断.我们看到的这个方法比较简单,但是实际的业务可能比这复杂边界,比这个判断的啊要更多一些. 就从类的层次上进行拆分
public class FlyBird {
public void mainMoveMode(String birdName){
System.out.println(birdName+"用翅膀飞");
}
}
public class WalkBird {
public void mainMoveMode(String birdName){
System.out.println(birdName+"用脚走");
}
}
FlyBird flyBird = new FlyBird();
flyBird.mainMoveMode("大雁");
WalkBird walkBird = new WalkBird();
walkBird.mainMoveMode("鸵鸟");
我们把一个类啊进行拆分这样就使得每一个类里面的方法实则是单一的比较简单,也不至于那修改的时候引入新的问题
接口的拆分
public interface ICourse {
String getCourseName(); //获取名字
byte[] getCourseVideo(); //获取时长
void studyCourse(); //学习课程
void refundCourse(); //返回课程大小
}
这个接口啊可不是只有一个职责,首先它一个大职责是获得和课程信息,另外一个职责是管理课程和课程内容无关,比如学习课程,如果我们学习课程就要获取内幕视频呢?那如果退了这个课程呢,可能就获取不了这个名字和大小了,因为这个课程,已经被我退掉了,那就是说退这个课程会影响课程,这个接口的获取课程内容相关的。这两个职责,是互相影响的\那现在呢,这两个职责例如说我退了这个课程,那获取课程信息的时候啊我们这个实现获取不到
那我们就可以把这个接口拆成两个接口,一个是获取课程信息的一个接口,另外是课程管理方面的一个。
public interface ICourseManager {
void studyCourse();
void refundCourse();
}
public interface ICourseContent {
String getCourseName();
byte[] getCourseVideo();
}
实现类
public class CourseImpl implements ICourseManager,ICourseContent {
@Override
public void studyCourse() {
}
@Override
public void refundCourse() {
}
@Override
public String getCourseName() {
return null;
}
@Override
public byte[] getCourseVideo() {
return new byte[0];
}
}
这两个职责呢也是单一的,也就是说我们可以通过实现一个接口或者多个接口来组合出这个实现类的一个具体实现,我们也可以实现一个接口职责是清晰隔离的,也就是说我们这个时限内实现什么职责,都是有清楚明确的定义,也是降低了复杂性,也就提高了可读性,提高了也就更容易维护了维护性的。同时变更引起的风险降低,一个接口的修改只对相应的实现类有影响,与其他的接口无关,这一点呢对项目的帮助还是非常大的 .
方法的单一职能
private void updateUserInfo(String userName,String address,boolean bool){
if(bool){
//todo something1
}else{
//todo something2
}
userName = "geely";
address = "beijing";
}
很明显的如果是true的话 做某些事情 ,false的话做某些事情, 那我们就可以拆分成两个方法
private void updateUsername(String userName){
userName = "geely";
}
private void updateUserAddress(String address){
address = "beijing";
}
如果使用的话就建议拆开,因为有布尔类型在这方法名命名上,可能啊,也不会很好的能表达。单一的一个职责,这样的开发起来简单维护起来也容易,
如果我们没有面向接口编程而又非常良好的遵循单一职责,有可能引起类的一个大爆炸那类的数量会比较多,所以呢我们在总结起来啊,就是说在实际的开发中啊,我们的接口和方法一定要做到单一职责,因为呢这个其实还是蛮好的,对于我们维护起来也会比较方便
接口隔离原则
>定义:用多个专门的接口,而不使用单一的总接口,
客户端不应该依赖它不需要的接口
一个类对一个类的依赖应该建立在最小的接口上
建立单一接口,不要建立庞大臃肿的接口
尽量细化接口,接口中的方法尽量少
public interface IAnimalAction {
void eat();
void fly();
void swim();
}
public class Dog implements IAnimalAction {
@Override
public void eat() {
}
@Override
public void fly() {
}
@Override
public void swim() {
}
}
我们可以看到一个接口 里面有三种方法, 当狗实现了这个接口时候,那么它的fly方法是会为空的, 所以对接口进行了拆分
public interface IEatAnimalAction {
void eat();
}
public interface IFlyAnimalAction {
void fly();
}
public interface ISwimAnimalAction {
void swim();
}
所以我们的狗就变成了这样子的
public class Dog implements ISwimAnimalAction,IEatAnimalAction {
@Override
public void eat() {
}
@Override
public void swim() {
}
}
但是如果接口设计得过小,也就是说里边的方法过少则会造成接口数量过多,提高整个程序设计的复杂性。所以要适度使用
优点是什么?符合我们常说的高内聚低耦合的设计思想,从而使得内聚有很好的可读性,可扩展性,还有可维护性,那我们平时。就在设计接口的时候呢,只暴露给调用的类他需要的方法,他不需要的方法呢,则隐藏起来只有专注的为一个模块提供好定制服务才能建立最小的依赖关系从一定程度上减少了耦合降低依赖关系,减少对外的交互,是接口中最少的方法去完成最多的事情,就是高内聚的一个体现。
那我们运用接口隔离原则一定要适度,接口设计得过大或者过小都不好,我们在设计接口的时候,要多花些时间去思考和筹划。
才能准确的实践这一原则,那在实际的项目开发当中啊,在实现接口隔离原则的时候,我们也要考虑业务模型,包括有可能以后会发生变更的地方,我们还要做一些预判。
与单一职能原则的区别
单一职责原则指的是接口和方法的职责是单一的,强调的是职责。也就是说在一个接口里,只要职责是单一的有多个方法也可以 例如说吃这种呢,有很多吃法对吧,例如游泳我可以狗刨,或者仰泳什么的。
那接口隔离原则,注重的是对接口依赖的隔离,注意我们这几个接口已经隔离开了,那单一职责呢,约束的是类接口和方法,接口隔离原则主要约束的就是接口.是针对抽象,针对程序整体框架的一个构建.
迪米特原则
◆定义:一个对象应该对其他对象保持最少的了解。又叫最少知道原则
◆尽量降低类与类之间的耦合
优点:降低类之间的耦合
◆强调只和朋友交流,不和陌生人说话
◆朋友:
出现在成员变量、方法的输入、输出参数中的类称为成员朋友类,
而出现在方法体内部的类不属于朋友类。
Demo 我们以一个Boss 给TeamLeader 下问说有多少个员工在上班
public class Boss {
public void commandCheckNumber(TeamLeader teamLeader){
List<Course> courseList = new ArrayList<Course>();
for(int i = 0 ;i < 20;i++){
courseList.add(new Course());
}
teamLeader.checkNumberOfCourses(courseList);
}
}
TeamLeader类 :
public class TeamLeader {
public void checkNumberOfCourses(List<Course> course){
System.out.println("员工的数量是:"+course.size());
}
}
Test类 :
public static void main(String[] args) {
Boss boss = new Boss();
TeamLeader teamLeader = new TeamLeader();
boss.commandCheckNumber(teamLeader);
}
我们可以看到,Boss 在boss 里面创建了Course 类,但是从原则上讲员工是跟TeamLeader 对接的 根据这个最少知道原则 ,那么Boss只需要知道TeamLeader就行了不需要Course类, 从类图上看, 可以看出Boss既知道TeamLeader又知道 员工类
所以我们对代码进行改造
BOSS类
public class Boss {
public void commandCheckNumber(TeamLeader teamLeader){
teamLeader.checkNumberOfCourses();
}
}
public class TeamLeader {
public void checkNumberOfCourses(){
List<Course> courseList = new ArrayList<Course>();
for(int i = 0 ;i < 20;i++){
courseList.add(new Course());
}
System.out.println("在线课程的数量是:"+courseList.size());
}
}
对于我们Boss类讲只要问TeamLeader就行了,不需要知道其他的细节 最重要的是要区分哪些是我们直接的朋友,哪些是不需要关注的类
里式替换原则
定义:
如果对每一个类型为T1的对象01,都有类型为T2的对象02,
使得以T1定义的所有程序P在所有的对象01都替换成02时,
程序P的行为没有发生变化,那么类型T2是类型T1的子类型。
定义扩展:
一个软件实体如果适用一个父类的话那一定适用于其子类
所有引用父类的地方必须能透明地使用其子类的对象,子类对象能够
替换父类对象,而程序逻辑不变。
它是对实现抽象化的具体步骤规范,注意规范。它来约束我们,那对于这个定义,我们反着想我们打个比方。比如说我们自己继承了arraylist,写了一个list,那通常我们用list去获取元素的时候,我们调用get()方法,里面呢要传一个index,所以下小标OK那默认的JDK哪都是从0 开始的,
我们继承了arraylist之后我们重写了各种方法,让他从第1个元素开始呢,这样当别人用我们写的继承arraylist的那个mylist去调用get的方法的时候,就会得到不一样的结果,也就是,程序p的行为发生了变化,那么我们认为我们自己写的list就不、不是arraylist的子类型 。
里是替换原则,就是表达了反对子类重写父类方法的这一层。含义,那继承作为面向对象的特性之一,给我们设计程序的时候带来了巨大的便利,同时也带来了弊端,比如我们使用继承会给程序带来一些侵入性,可移植性呢也会降低,增加了对象间的耦合。
那如果一个父类被很多子类继承,假设我们修改这个父类的时候,必须要考虑所有的子类,而且在修改父类之后,如果没有考虑子类的话,很容易给我们的系统呢,引入一些风险发生故障。
里氏替换原则
引申意义:子类可以扩展父类的功能,但不能改变父类原有的功能。
含义1:子类可以实现父类的抽象方法,但不能覆盖父类的非抽象方法。
含义2:子类中可以增加自己特有的方法。
含义3:当子类的方法重载父类的方法时,方法的前置条件
即方法的输入/入参)要比父类方法的输入参数更宽松。
含义4 :当子类的方法实现父类的方法时(重写/重载或实现抽象方法) .
方法的后置条件(即方法的输出/返回值)要比父类更严格或相等。
举个例子 : 假设父类是抽象类,有一个方法返回值是一个map,那么子类在实现这个抽象方法的时候返回值。要么是map(和父类相等)要么呢比父类更严格,比如HashMap,
我们回到我们第一个的例子 我们打折的时候直接调用的是父类的getprice()*0.8 这样的做法是违反里氏替换原则的,因为我们覆盖了父类的非抽影方法,
所以我们改成了这样
public class JavaDiscountCourse extends JavaCourse {
public JavaDiscountCourse(Integer id, String name, Double price) {
super(id, name, price);
}
public Double getDiscountPrice(){
return super.getPrice()*0.8;
}
}
那看到里氏替换原则有这么多条条框框,而我们实际在编程中 可能没有注意这些,而我们的程序呢,跑的也好好的那我们想一下,如果我们就不追寻里氏替换原则会怎么样呢?因为我们的代码中呢,也有违反里氏替换原则的地方,那这个后果可能就是会导致,我们在一些需求变更引入新功能,或者在进行重构的时候问题的风险就会增加,里氏替换原则是一个非常好的约束
里氏替换原则约束继承泛滥,同时呢也是开闭原则的一种体现,为实现开闭原则提供了步骤规范,打个比方,防止基层泛滥的一个比方。
假设我们有一个父类,使人有个方法生娃,那子类呢可能是各大洲,亚洲人,非洲人,欧洲人,他们呢都能继承生娃这个行为,这里也就满足了里氏替换原则,那同样的。
父类是人有个方法生娃行为,子类呢有超人,有变形金刚,有蜘蛛侠,还有那机器人,那人的基本特征,也都有,但是如果我们让机器人去生娃的话。他还不具备这个行为,也许在以后的很多年,有可能具备这个行为,但是就目前来看呢,机器人不能成为人的这个父类的子类,所以,我们在选择使用继承的时候。还是要慎重选择,还是要分析它们的属性,特征,行为这些因素的,结合我们实际开发的时候,更是要把我们业务模型中的东西抽象到我们代码当中。我们满足替换原则,则会加强程序的健壮性,同时在变更时也可以做到非常好的兼容性,提高程序的维护性,扩展性,降低需求,变更时引入的风险,包括扩展,增加新功能等。
业务场景:长方形和正方形的关系,我们会认为正方形是特殊的长方形,只不过它的长和宽是相等的,如果我们认为正方形是一种特殊的长方形,那我们就可以建立一个父类长方形正方形的,来继承这个长方形
public class Rectangle {
//长方形
private long length;
private long width;
public long getLength() {
return length;
}
public long getWidth() {
return width;
}
public void setLength(long length) {
this.length = length;
}
public void setWidth(long width) {
this.width = width;
}
}
正方形 :
public class Square extends Rectangle {
private long sideLength; //边长
public long getSideLength() {
return sideLength;
}
public void setSideLength(long sideLength) {
this.sideLength = sideLength;
}
@Override
public void setLength(long length) {
setSideLength(length);
}
@Override
public void setWidth(long width) {
setSideLength(width);
}
@Override
public long getWidth() {
return getSideLength();
}
@Override
public long getLength() {
return getSideLength();
}
}
调整它们的大小,这里面呢会调整长和宽进行相等,就是说当宽小于长的时候或者等于长的时候,都会给宽度啊进行加1,直到它们相等,退出循环,是一个变大的过程
public static void resize(Rectangle rectangle){
while (rectangle.getWidth() <= rectangle.getLength()){
rectangle.setWidth(rectangle.getWidth()+1);
System.out.println("width:"+rectangle.getWidth() + " length:"+rectangle.getLength());
}
System.out.println("resize方法结束 width:"+rectangle.getWidth() + " length:"+rectangle.getLength());
}
程序运行到21 的时候长方形会停止 ,那我们换成正方形的时候呢?
public static void main(String[] args) {
Rectangle rectangle = new Rectangle();
rectangle.setWidth(10);
rectangle.setLength(20);
resize(rectangle);
}
它会一直运行下去直到程序退出
当我们把父类替换成子类进行执行的时候,程序运行的期望和我们所期望的是不一样的,运行的结果呢,和之前传的长方形。正方形是不可以成为长方形的子类的。违反了里氏替换原则的设计,那我们怎么办呢?可以在创建一个新的类,而解除长方形和正方形的继承关系。
比如说我们写了很多关于鸟的一些业务,包括飞呀等等之类的,那这个时候我们有一个新的此类鸵鸟,那他呢又不会飞,那就要看我们的业务模型,对于关于飞的这个行为现有的业务模型的抽象及继承关系是否合理,从而决定这里是否需要重构,假设我们认为飞就是鸟的特性,那鸵鸟呢,它就是不能飞的,如果我们。强行的让鸵鸟继承鸟这个类的话,那最终肯定还会导致我们的代码出错
合成(组合)/聚合复用原则
定义:尽量使用对象组合/聚合,而不是继承关系达到软件复用的目的
◆聚合has-A和组合contains-A 继承是is—A
优点:可以使系统更加灵活,降低类与类之间的耦合度
一个类的变化对其他类造成的影响相对较少
实例:数据库连接 我们业务层去继承这个数据库连接
/**
* 数据库连接抽象类
*/
public abstract class DBConnection {
public String getConnection(){
return "MySQL数据库连接";
}
}
public class ProductDao extends DBConnection{
public void addProduct(){
String connection = super.getConnection();
System.out.println("使用"+connection+"增加产品");
}
}
使用层
public class Test {
public static void main(String[] args) {
ProductDao productDao = new ProductDao();
productDao.addProduct();
}
}
这是我们现在的类图 ,但是我们现在要接入一个新数据库连接,当然我们可以在BdConnecticut中在加一个方法,但是这样会违反开闭原则 新增一个抽象接口 里面的抽象方法
public abstract class DBConnection {
public abstract String getConnection();
}
public class MySQLConnection extends DBConnection {
@Override
public String getConnection() {
return "MySQL数据库连接";
}
}
public class PostgreSQLConnection extends DBConnection { @Override public String getConnection() { return "PostgreSQL数据库连接"; } }
public abstract class ProductDao extends DBConnection {
private DBConnection dbConnection;
public void setDbConnection(DBConnection dbConnection) {
this.dbConnection = dbConnection;
}
public void addProduct(){
String conn = dbConnection.getConnection();
System.out.println("使用"+conn+"增加产品");
}
}
public static void main(String[] args) {
ProductDao productDao = new ProductDao() ;
productDao.setDbConnection(new PostgreSQLConnection());
productDao.addProduct();
}