Java最佳实践和建议:设计模式

栏目: 后端 · 发布时间: 5年前

内容简介:设计模式是软件开发过程中经常出现的问题的常见解决方案。这些解决方案提供了优雅且在大多数情况下解决与对象创建,资源分配,简化代码等相关的不同问题的最有效方法。需要维护它们的上下文,而解决方案本身需要定制,根据业务逻辑。设计模式分为三类:

设计模式是软件开发过程中经常出现的问题的常见解决方案。这些解决方案提供了优雅且在大多数情况下解决与对象创建,资源分配,简化代码等相关的不同问题的最有效方法。需要维护它们的上下文,而解决方案本身需要定制,根据业务逻辑。

设计模式分为三类:

  • 创造性,提供解决方案来解决在对象创建过程中发生的不同问题
  • 结构化,通过找到如何在更大的结构中组合类的方法,为实例化问题提供解决方案
  • 行为,为代码的不同部分之间的通信中发生的问题提供解决方案。

DAO模式

在架构设计过程中,一些 设计模式 实际上可以用作指导,就像DAO设计模式的情况一样。软件体系结构通常有三层:应用程序的端点,服务层,即业务逻辑和数据层。

数据层是使用DAO设计模式(数据访问对象)实现的,该模式将与数据库通信的部分与应用程序的其余部分分开。DAO模式定义了所有实体的CRUD(创建,读取,更新,删除)操作。通过添加将经常用于实体本身的命名/本机查询,可以完全分离持久层。

<b>public</b> <b>interface</b> DAO<T,E <b>extends</b> Serializable>{
  <b>public</b> T save(T object);
  <b>public</b> Boolean delete(T object);
  <b>public</b> T update(T object);
  <b>public</b> T find(E id);
}

DAO的接口本身仅定义了需要在实现中指定的操作。实现本身使用提供的实体管理器的泛型类型。实体管理器是一个负责应用程序中所有持久性操作的类,可以使用应用程序上下文获取。

<b>public</b> <b>abstract</b> <b>class</b> GenericDAO<T,E> implements DAO<T,E>{
  @PersistenceContext  
  <b>private</b> EntityManager entityManager;  
<b>public</b> T save(T object){
    <b>return</b> entityManager.persist(object);
  }
<b>public</b> T find(E id){
    <b>return</b> entityManager.find(T.<b>class</b>,id);
  }
<b>public</b> Boolean delete(T object){
    <b>return</b> entityManager.remove(object);
  }
<b>public</b> T update(T object){
    <b>return</b> entityManager.merge(object);
  }
}

提供的示例需要基本了解Hibernate和 Java 的持久性。Hibernate是一个ORM工具(对象关系映射),它从java代码创建表,并使用HQL(休眠查询语言)进行查询输入和执行。

@Entity
@Table(name=<font>"person"</font><font>)
@NamedQueries
(
 {
  @NamedQuery(name=Person.GET_PERSON_BY_AGE,query=</font><font>"Select * from 
  User u where u.age>:age</font><font>")
 }
)
<b>public</b> <b>class</b> Person{
 
  <b>public</b> <b>static</b> <b>final</b> String GET_PERSON_BY_AGE =   
  </font><font>"Person.getPersonByAge"</font><font>;
  @Id
  @GeneratedValue( strategy = GenerationType.IDENTITY)
  @Column(name=</font><font>"id"</font><font>,unique=</font><font>"true"</font><font>)
  <b>public</b> <b>int</b> id;
  @Column(name=</font><font>"name"</font><font>)
  <b>public</b> String name;
  <b>public</b> Person(String name){
    <b>this</b>.name=name;
  }
  </font><font><i>//getters and setters...</i></font><font>
}
</font>

将用于实体的DAO类扩展了通用DAO,其中实现了基本的CRUD操作,因此我们只需要添加将要使用的特定查询。

<b>public</b> PersonDAO <b>extends</b> GenericDAO<Person,Integer>{
<b>public</b> List<Person> getPersonByAge(<b>int</b> age){
    Query q=entityManager.createNamedQuery(Person.GET_PERSON_BY_AGE,
    Person.<b>class</b>);
    q.setParameter(<font>"age"</font><font>,5);
    <b>return</b> (List<Person>)q.getResultList();
  }
}
</font>

优点:

  • 提供代码与业务逻辑的逻辑和物理分离,易于实现;
  • 可以使用缓存策略轻松扩展DAO类,可以在方法中实现;
  • 如果将DAO类声明为EJB,则每个方法都可以指定事务属性,以便控制底层事务的范围;

缺点:

  • 它会在与数据库的连接中产生开销,因为DAO对象通常会处理整个对象。当涉及到保存操作时,这是一个优点,因为整个对象一次存储但是读取可能是昂贵的操作;
  • 为了避免这种情况,可以使用本机或命名查询,以便根据业务需要检索对象的较小部分;
  • DAO模式不应该在小型应用程序中使用,因为它的优点很小,而且代码会变得更复杂;

工厂模式

设计模式通常用于简化大块代码,甚至可以隐藏应用程序流中的特定实现。这类问题的完美示例是工厂设计模式,它是一种创造性设计模式,无需指定对象的确切类别即可提供对象创建。它建议使用从超类继承的超类和多个子类。在执行期间,仅使用超类,其值因工厂类而异。

<b>public</b> <b>class</b> Car{

  <b>private</b> String model;
  <b>private</b> <b>int</b> numberOfDoors;
  <b>public</b> Car(){
  }
  <b>public</b> String getModel(){
    <b>return</b> <b>this</b>.model;
  }
  <b>public</b> <b>int</b> getNumberOfDoors(){
    <b>return</b> <b>this</b>.numberOfDoors;
  }
  <b>public</b> <b>void</b> setModel(String model){ <b>this</b>.model = model; }
  <b>public</b> <b>void</b> setNumberOfDoors(<b>int</b> n){ <b>this</b>.numberOfDoors = n; }
}
<b>public</b> <b>class</b> Jeep <b>extends</b> Car{
  <b>private</b> <b>boolean</b> land;
  <b>public</b> Jeep(){
  }
 
  <b>public</b> <b>void</b> setLand(<b>boolean</b> land){
    <b>this</b>.land=land;
  }
  <b>public</b> <b>boolean</b> getLand(){
    <b>return</b> <b>this</b>.land;
  }
}

<b>public</b> <b>class</b> Truck <b>extends</b> Car{
  <b>private</b> <b>float</b> capacity;
  <b>public</b> Truck(){
  }
  <b>public</b> <b>void</b> setCapacity(<b>float</b> capacity){
    <b>this</b>.capacity=capacity;
  }
  <b>public</b> <b>float</b> getCapacity(){
    <b>return</b> <b>this</b>.capacity;
  }
}

为了使用这种模式,我们需要实现一个工厂类,它将为给定的输入返回正确的子类。上面的java类指定了一个超类(Car.java)和两个子类(Truck.java和Jeep.java)。在我们的实现中,我们实例化Car类的一个对象,并且根据参数,工厂类将决定它是Jeep还是Truck。

<b>public</b> <b>class</b> CarFactory{
   <b>public</b> Car getCarType(<b>int</b> numberOfDoors, String model,Float
   capacity, Boolean land){ 
      Car car=<b>null</b>;      
         <b>if</b>(capacity!=<b>null</b>){
            car=<b>new</b> Jeep();
            <font><i>//implement setters</i></font><font>
         }<b>else</b>{
            car=<b>new</b> Truck();
            </font><font><i>//implement setters</i></font><font>
         }
     <b>return</b> car;
   }
}
</font>

在运行时,工厂类考虑输入实例化正确的子类。

<b>public</b> <b>static</b> <b>void</b> main(String [] args){ 
  Car c = <b>null</b>; 
  CarFactory carFactory = <b>new</b> CarFactory(); 
  c = carFactory.getCarType(2,“BMW”,<b>null</b>,<b>true</b>); 
}

抽象工厂

抽象工厂设计模式以相同的方式工作,但父类不是常规类,而是一个抽象类。抽象类通常更快,更容易实例化,因为它们基本上是空的。实现是相同的,只有父类被声明为抽象及其所有方法,并且子类需要实现抽象类中声明的方法的行为。

Abstract工厂的示例是使用接口创建的。通过简单地用抽象类替换接口可以完成同样的操作,而不是实现接口,子类将扩展抽象类。

<b>public</b> <b>interface</b> Car {
   
   <b>public</b> String getModel();
   <b>public</b> Integer getNumberOfDoors();
   <b>public</b> String getType();
}
<b>public</b> <b>class</b> Jeep implements Car{
   <b>private</b> String model;
   <b>private</b> Integer numberOfDoors;
   <b>private</b> Boolean isLand;
   <b>public</b> Jeep() {}
   <b>public</b> Jeep(String model, Integer numberOfDoors, Boolean isLand){
     <b>this</b>.model = model;
     <b>this</b>.numberOfDoors = numberOfDoors;
     <b>this</b>.isLand = isLand;
   }
   <b>public</b> String getModel(){
     <b>return</b> model;
   }
   <b>public</b> Integer getNumberOfDoors() {
     <b>return</b> numberOfDoors;
   }
   <b>public</b> Boolean isLand() {
     <b>return</b> isLand;
   }
   <b>public</b> <b>void</b> setLand(Boolean isLand) { <b>this</b>.isLand = isLand; }
  
   <b>public</b> <b>void</b> setModel(String model) { <b>this</b>.model = model; }
  
   <b>public</b> <b>void</b> setNumberOfDoors(Integer numberOfDoors){    
      <b>this</b>.numberOfDoors = numberOfDoors; 
   }
   <b>public</b> String getType(){
      <b>return</b> <font>"jeep"</font><font>;
   }

<b>public</b> <b>class</b> Truck implements Car{
   <b>private</b> String model;
   <b>private</b> Integer numberOfDoors;
   <b>private</b> Integer numberOfWheels;
   <b>public</b> Truck(String model, Integer numberOfDoors, Integer numberOfWheels) {
      <b>this</b>.model = model;
      <b>this</b>.numberOfDoors = numberOfDoors;
      <b>this</b>.numberOfWheels = numberOfWheels;
   }
   <b>public</b> Truck() {}
   <b>public</b> String getModel() { <b>return</b> model; }
   <b>public</b> Integer getNumberOfDoors() { <b>return</b> numberOfDoors; }
   <b>public</b> Integer getNumberOfWheels() { <b>return</b> numberOfWheels; }
   <b>public</b> <b>void</b> setNumberOfWheels(Integer numberOfWheels) {
     <b>this</b>.numberOfWheels = numberOfWheels;
   }
   <b>public</b> <b>void</b> setModel(String model) { <b>this</b>.model = model; }
   <b>public</b> <b>void</b> setNumberOfDoors(Integer numberOfDoors) {
      <b>this</b>.numberOfDoors = numberOfDoors;
   }
   <b>public</b> String getType(){ <b>return</b> </font><font>"truck"</font><font>; }
}
<b>public</b> <b>class</b> CarFactory {
   <b>public</b> CarFactory(){}
   <b>public</b> Car getCarType(String model,Integer numberOfDoors, Integer numberOfWheels, Boolean isLand){
     <b>if</b>(numberOfWheels==<b>null</b>){
        <b>return</b> <b>new</b> Jeep(model,numberOfDoors,isLand);
     }<b>else</b>{
        <b>return</b> <b>new</b> Truck(model,numberOfDoors,numberOfWheels);
     }
   }
}
</font>

唯一的区别是抽象类中声明的方法必须在每个子类中实现。在这两种情况下,工厂和主要方法都保持不变。

<b>public</b> <b>class</b> CarMain {
   <b>public</b> <b>static</b> <b>void</b> main(String[] args) {
      Car car=<b>null</b>;
      CarFactory carFactory=<b>new</b> CarFactory();
      car=carFactory.getCarType(<font>"Ford"</font><font>, <b>new</b> Integer(4), <b>null</b>, <b>new</b> Boolean(<b>true</b>));
     System.out.println(car.getType());
    }
}
</font>

优点:

  • 它允许松散耦合和更高级别的抽象;
  • 它是可扩展的,可用于将某些实现与应用程序分开;
  • 通过简单地添加适当的实例化逻辑,可以在层次结构中创建新类之后重用工厂类,并且代码仍然可以工作。
  • 单元测试,因为使用超类可以很容易地覆盖所有场景;

缺点:

  • 它往往太抽象,难以理解;
  • 了解何时实现工厂设计模式非常重要,因为在小型应用程序中,它只会在对象创建期间创建开销(更多代码);
  • 工厂设计模式必须保持其上下文,即只有从同一父类继承或实现相同接口的类才适用于工厂设计模式。

singleton单例模式

这个设计模式是最有名的和有争议的造物设计模式之一。单例类是一个类,它将在应用程序的生命周期中仅实例化一次,即只有一个对象共享所有资源。单例方法是线程安全的,并且可以由应用程序的多个部分同时使用,即使它们访问Singleton类中的共享资源也是如此。关于何时使用单例类的完美示例是记录器实现,其中所有资源都在同一日志文件中写入并且是线程安全的。其他示例包括数据库连接和共享网络资源。

此外,每当应用程序需要从服务器读取文件时,使用Singleton类就很方便,因为在这种情况下,只有应用程序的一个对象才能访问存储在服务器上的文件。除了记录器实现之外,配置文件是使用单例类有效的另一个示例。

在java中,singleton是一个带有私有构造函数的类。单例类使用类本身的实例保留一个字段。该对象是使用get方法创建的,如果尚未启动实例,则调用构造函数。早些时候,我们提到过这种模式最具争议性,因为实例生成的多个实现。它必须是线程安全的,但它也必须是高效的。在示例中,我们有两个解决方案。

<b>import</b> java.nio.file.Files;
<b>import</b> java.nio.file.Paths;
<b>public</b> <b>class</b> LoggerSingleton{
<b>private</b> <b>static</b> Logger logger;
    <b>private</b> String logFileLocation=<font>"log.txt"</font><font>;
    <b>private</b> PrintWriter pw;
    <b>private</b> FileWriter fw;
    <b>private</b> BufferedWriter bw;
    <b>private</b> Logger(){
       fw = <b>new</b> FileWriter(logFileLocation, <b>true</b>);
       bw = <b>new</b> BufferedWriter(fw)
       <b>this</b>.pw = <b>new</b> PrintWriter(bw);
    }
    
   <b>public</b> <b>static</b> synchronised Logger getLogger(){
       <b>if</b>(<b>this</b>.logger==<b>null</b>){
          logger=<b>new</b> Logger();
       }
       <b>return</b> <b>this</b>.logger;
    }
 
    <b>public</b> <b>void</b> write(String txt){
       pw.println(txt);
    }
}
</font>

因为将经常访问日志文件。使用缓冲写入器的打印编写器确保文件不会多次打开和关闭。

第二个实现包括一个私有类,它包含Singleton类实例的静态字段。私有类只能在单例类中访问,即只能从get方法访问。

<b>public</b> <b>class</b> Logger{
    
    <b>private</b> <b>static</b> <b>class</b> LoggerHolder(){
         <b>public</b> <b>static</b> Singleton instance=<b>new</b> Singleton();
    }
    
    <b>private</b> Logger(){
    <font><i>// init</i></font><font>
    }
   
    <b>public</b> <b>static</b> Logger getInstance(){
        <b>return</b> LoggerHolder.instance;
    }
}
</font>

然后可以从app中的任何其他类使用单例类:

Logger log=Logger.getInstance();
log.write(<font>"something"</font><font>);
</font>

优点:

  • 单例类只在应用程序的生命周期中实例化一次,并且可以多次使用;
  • singleton类允许线程安全访问共享资源;
  • 单例类不能扩展,如果正确实现,即get方法应该是同步和静态的,它是线程安全的;
  • 建议首先创建一个接口,然后设计单例类本身,因为它更容易测试接口;

缺点:

  • 测试期间的问题,当单例类访问共享资源并且测试的执行很重要时;
  • 单例类还隐藏了代码中的一些依赖项,即创建未明确创建的依赖项;
  • 使用没有工厂模式的单例的问题在于它打破了单一责任原则,因为类正在管理自己的生命周期;

Builder模式

生成器模式也是创建模式,它允许对复杂对象的增量创建。当字段设置需要复杂操作或仅仅字段列表太长时,建议使用此模式。该类的所有字段都保存在私有内部类中

<b>public</b> <b>class</b> Example{
   <b>private</b> String txt;
   <b>private</b> <b>int</b> num;
   <b>public</b> <b>static</b> <b>class</b> ExampleBuilder{
      <b>private</b> String txt;
      <b>private</b> <b>int</b> num;
      <b>public</b> ExampleBuilder(<b>int</b> num){
          <b>this</b>.num=num;
      }
        
      <b>public</b> ExampleBuilder withTxt(String txt){
          <b>this</b>.txt=txt;
          <b>return</b> <b>this</b>;
      }
      <b>public</b> Example build(){
          <b>return</b> <b>new</b> Example(num,txt);
      }
  }
  
  <b>private</b> Example(<b>int</b> num,String txt){
     <b>this</b>.num=num;
     <b>this</b>.txt=txt;
  }
}

在实际情况中,参数列表将更长,并且可以基于其他输入计算类的一些参数。构建器类与类本身具有相同的字段,并且必须将其声明为静态才能访问,而无需实例化持有者类的对象(在本例中为Example.java)。上面给出的实现是线程安全的,可以通过以下方式使用:

Example example=<b>new</b> ExampleBuilder(10).withTxt(<font>"yes"</font><font>).build();
</font>

优点:

  • 如果类中的参数数量大于6或7,则代码更加整洁和可重用;
  • 在设置所有需要的字段之后创建对象,并且只有完全创建的对象可用;
  • 构建器模式隐藏构建器类中的一些复杂计算,并将其与应用程序流分离;

缺点:

  • 构建器类必须包含原始类中的所有字段,因此与单独使用类相比,可能需要更多的时间来开发;

观察模式

观察 设计模式是一种行为设计模式,它通过将某些实体传播到应用程序的相关部分来观察某些实体并处理这些更改。每个容器可以为不同的设计模式提供不同的实现,并且观察者模式在java中使用接口Observer来实现,该接口将受到观察者类中的更改的影响。另一方面,观察到的类需要实现Observable接口。Observer接口只有update方法,但在Java 9中已弃用,因为它的简单性不建议使用它。它没有提供有关更改内容的详细信息,只是在较大的对象中查找更改可能是一项代价高昂的操作。


以上就是本文的全部内容,希望对大家的学习有所帮助,也希望大家多多支持 码农网

查看所有标签

猜你喜欢:

本站部分资源来源于网络,本站转载出于传递更多信息之目的,版权归原作者或者来源机构所有,如转载稿涉及版权问题,请联系我们

程序员修炼之道

程序员修炼之道

Andrew Hunt、David Thomas / 马维达 / 电子工业出版社 / 2011-1 / 55.00元

《程序员修炼之道:从小工到专家》内容简介:《程序员修炼之道》由一系列独立的部分组成,涵盖的主题从个人责任、职业发展,知道用于使代码保持灵活、并且易于改编和复用的各种架构技术,利用许多富有娱乐性的奇闻轶事、有思想性的例子及有趣的类比,全面阐释了软件开发的许多不同方面的最佳实践和重大陷阱。无论你是初学者,是有经验的程序员,还是软件项目经理,《程序员修炼之道:从小工到专家》都适合你阅读。一起来看看 《程序员修炼之道》 这本书的介绍吧!

RGB转16进制工具
RGB转16进制工具

RGB HEX 互转工具

随机密码生成器
随机密码生成器

多种字符组合密码