• 模式


    public class Constants{

      public static final String CONSTANT_PATHS="";

    }

    如 java.io.ObjectStreamConstans,多是出现在 Enum 类型到来之前。

    其实 Java 的接口常量是一种反模式,理由如下:

    1. 接口是不能阻止被实现或继承的,也就是说子接口或实现中是能够覆盖掉常量的定义,这样通过父,子接口(或实现) 去引用常量是可能不一致的

    2. 同样的,由于被实现或继承,造成在继承树中可以用大量的接口, 类 或实例去引用 同一个常量,从而造成接口中定义的常量污染了命名空间。(Java 编译器竟然允许使用实例去引用类变量)

    3. 接口暗含的意思是:它是需被实现的,代表着一种类型,它的公有成员是要被暴露的 API。而在接口中定义的常量说不上是 API

    既然接口中不适于定义常量,那么该在何处为常量安家呢?接口为 实现/继承 而生,如果放在类中,并且这个类是 final,且封闭掉构造方法就行。于是我们先前的接口常量定义

     
     

    在应用中,我们往往需要一个常量文件,用于存储被多个地方引用的共享常量。在设计应用时,我也遇到了类似的情况,很多地方都需要各种各样的常量。

    我确定需要一个单独的文件来存储这些静态公共常量。但是我不是特别确定是应该用接口还是类(枚举不满足我的需求)。我有两种选择:

    使用接口,如:

    package one;public interface Constants {String NAME="name1";int MAX_VAL=25;} 

    package two;public class Constants {public static final String NAME="name1";public static final int MAX_VAL=25;} 

    我的观点是使用接口。因为接口会自动将成员变量设置为静态的(static)、不可变的(final),这一点可以防止某些情况下错误地添加新的常量。这也使得代码看起来更简单和清晰。

    同时,一个的简单测试显示,同样的接口(字节码文件)占用的空间是209个字节(ubuntu 14.04机器上),而类(字节码文件)占用的空间是366个字节(同样的操作系统)。更少的字节码文件意味着加载和维护的成本更低。此外,JVM 加载接口的时候,不需要担心类提供的额外特征(如重载、方法的动态绑定等),因此加载更快。

    这看起来非常好,但是这是一个典型反模式的例子。虽然使用接口来保存常量看起很有帮助,但是这给应用后期的扩展留下一个漏洞。

    假设存在在一个类,紧密】依赖于这些常量。开发者在该类中写满了通过接口对常量的引用。如:

    packagename.Constant.CONSTANT_NAME 

    所以,为了“清理”这段代码,他可能想实现该接口,这样他就不需要到处写“packagename.Constants”,所有的常量可以直接访问。

    但是,一旦他实现了该接口,所有的常量就都变成“契约”(因为所有的常量都是公共的、静态的)的一部分。这导致为这个类增加了不必要的常量。这会动摇整个基础,并引起混乱。Java 中没有一种方式可以阻止类实现接口。

    而另一种方式,我们可以将类设置为final,这样就不能扩展。甚至,我们可以将构造器设置为私有的,以防止对这个类实例化,这样就永远不会破坏约定。此外,如果一个特殊的常量在同一个类中被多次使用,则开发者可以使用静态引入。

    所有对于常量类,比较好的设计应该是:

    package three;//make the class non-extendable by adding final 增加final关键字来避免继承public final class Constants {//Hide the constructor 隐藏构造器private Constants(){}public static String NAME="name";} 

    静态引入的例子:

    import static three.Constants.NAME;public class UseConstants {  public static void main(String[] args) {      System.out.println("the value of constants is"+NAME);  }} 

    这个设计问题也称为接口常量反模式(Constant Interface Anti-pattern)。

    在实际开发过程中,经常会需要定义一个文件,用于存储一些常量,这些常量设计为静态公共常量(使用 public static final 修饰)。这个时候就出现两种选择:

    1. 在接口中定义常量,比如 JDK 1.1 中的 java.io.ObjectStreamConstans 接口;
    2. 在类中定义常量,比如 JDK 1.7 中的 java.nio.charset.StandardCharsets

    这两种方式都能够达到要求:存储常量、无需实例化。下面分情况讨论下两种方式孰优孰劣。

    常量接口

    首先从代码的角度分析,接口中定义的变量都必须是常量,即默认使用 public static final 修饰。也就是说,在写代码的时候直接写成下面这样:

    public interface ObjectStreamConstants {
        short STREAM_MAGIC = (short)0xaced;
        short STREAM_VERSION = 5;
    }

    但是在类中就必须乖乖的写成下面这种样子:

    public final class ObjectStreamConstants {
        public static final short STREAM_MAGIC = (short)0xaced;
        public static final short STREAM_VERSION = 5;
    }

    从直观的感受,接口写起来方便多了。 第二个问题:因为类中写的字符比接口多,所以编译之后文件大小也是类文件比接口文件大。 第三个问题:在JVM加载过程中,接口没有类提供的额外特种(如重载、方法的动态绑定等),所以接口加载比类快。 分析到此,似乎没有什么理由不用接口定义常量了。但是,BUT,这种做法却是一种严重的反模式行为。 引用《Effective Java》中的一段描述:

    The constant interface pattern is a poor use of interfaces. That a class uses some constants internally is an implementation detail. Implementing a constant interface causes this implementation detail to leak into the class’s exported API. It is of no consequence to the users of a class that the class implements a constant interface. In fact, it may even confuse them. Worse, it represents a commitment: if in a future release the class is modified so that it no longer needs to use the constants, it still must implement the interface to ensure binary compatibility. If a nonfinal class implements a constant interface, all of its subclasses will have their namespaces polluted by the constants in the interface.

    翻译出来就是:

    常量接口模式是对接口的不良使用。类在内部使用某些常量,这纯粹是实现细节。实现常量接口会导致把这样的实现细节泄露到该类的导出API中。类实现常量接口,这对于类的用户来讲并没有什么价值。实际上,这样做反而会使他们更加糊涂。更糟糕的是,它代表了一种承诺:如果在将来的发行版本中,这个类被修改了,它不再需要使用这些常量了,它依然必须实现这个接口,以确保兼容性。如果非final类实现了常量接口,它的所有子类的命名空间也会被接口中的常量所“污染”。

    这样说来就很明白透彻了:

    1. 接口是不能阻止被实现或继承的,也就是说子接口或实现中是能够覆盖掉常量的定义,这样通过父、子接口(或实现) 去引用常量是可能不一致的;
    2. 同样的,由于被实现或继承,造成在继承树中可以用大量的接口、类或实例去引用同一个常量,从而造成接口中定义的常量污染了命名空间;
    3. 接口暗含的意思是:它是需被实现的,代表着一种类型,它的公有成员是要被暴露的API,但是在接口中定义的常量还算不上API。

    综上所述:使用接口定义常量,是一种不可取的行为。JDK中定义的接口常量(例如java.io.ObjectStreamConstans)应该算是反面教材。

    类接口

    既然使用接口第一常量不可取,那就只能通过类定义常量了。虽然在JAVA中类不能够多继承,但是子类也能够“污染”父类定义常量的命名空间。所以为了常量不可变,需要将常量类定义为final的,然后再彻底点就是再定义个private的构造函数。就像java.nio.charset.StandardCharsets一样:

    public final class StandardCharsets {
        private StandardCharsets() {
            throw new AssertionError("No java.nio.charset.StandardCharsets instances for you!");
        }
        public static final Charset US_ASCII = Charset.forName("US-ASCII");
        public static final Charset ISO_8859_1 = Charset.forName("ISO-8859-1");
        public static final Charset UTF_8 = Charset.forName("UTF-8");
    }

    java.nio.charset.StandardCharsets中,为了阻止各种形式的实例化,甚至在构造函数中抛出错误,也是做个够彻底的了。

    枚举类型

    但是,BUT,还有一种情况,比如常量中定义性别:男、女,使用上面的类常量,需要写成:

    public final class Gender {
        private Gender() {
            throw new AssertionError("No x.y.z.Gender instances for you!");
        }
        public static final int MALE = 1;
        public static final int FEMALE = 0;
    }

    因为定义的性别类型实际是int,如果手贱写成m.setGender(3)也是没有错误的,那3又是什么鬼?是不是还要有4567? 那这种常量定义就失去价值了。对于这种可以归类的常量,最好的常量定义方法应该就是枚举了:

    public enum Gender {
        MALE, 
        FEMALE
    }

    根据编辑的字节码,Gender实际是:

    public final class Gender extends java.lang.Enum {
        public static final Gender MALE;
        public static final Gender FEMALE;
    }

    这样对于接受 Gender 类型参数的方法就只能传入 MALE 或 FEMALE 了,不再有其他选项,这就是枚举的意义。在后来的JDK中,也出现了像java.nio.file.StandardOpenOption等的枚举定义。而且枚举的定义也不只局限于这种,还有很多其他复杂定义,可以适用各种情况,以后再慢慢讨论。

    结束语

    定义常量不要使用接口常量,要在类中定义,最好是final类,最还定义private的构造方法,如果常量可以进行归类,最好使用枚举定义:枚举 > 类 > 接口。

    常量

    “常量”在程序运行时,不会被修改的量。换言之,常量虽然是为了硬件、软件、编程语言服务,但是它并不是因为硬件、软件、编程语言而引入。
    常量区分为不同的类型,如25、0、-8为整形常量,6.8、-7.89为实型常量,‘a’‘b’为字符常量。常量一般从其字面形式即可判断。这种常量称为字面常量或直接常量。
    含义
    另一层含义指它们的编码方法是不变的,比如字符'A'无论在硬件、软件还是各种编程语言中,它的信息编码即为0x41,
    在除C/C++的一些高级程序语言中,常量可以被称作,在一个变化过程中,始终不变的量。它们可以是不随时间变化的某些量和信息,也可以是表示某一数值的字符或字符串,常被用来标识、测量和比较。
    但在C/C++语言中,常量如果还用运行过程中不变量来说,就显得不太准确了,甚至是在具体使用时,这是一个错误的概念。在C/C++中定义指针常量时,根据const的位置不同,可以定义出几个不同的形式,虽然他是符合常量的概念,但是在运行中,其值或地址值是会发生变化的。

    常量案例

    下面举几个C语言的例子做参考:
    int *p; /* 定义的是变量 */
    int const *p; /* 定义的就是常量 */
    int * const p; /* 定义的还是常量 */
    在上面定义的两个常量的句子中,都符合常量的定义,但是其值是有变化的。
    前者是表示定义的“指针常量”所指向的整数是不变的,在初始化时,指针变量假如指向地址为1000的变量a,这个a的值是固定不变的,但是指针常量p的值,即地址值可以向上向下移动指向不同的变量。这就是与常量概念相违背。
    后者是表示指针常量的地址值不变的,但是他所指向的整数是可以变化的,同样与上述的定义相违背。
    vb中的常量
    在某特定的时候,虽然声明了一个变量,但却不希望这个数值被修改,这种永不会被修改的变量,统称为常量。
    简单的说,就是在程序运行时,其值不能被改变的量。Visual Basic中的常量分为文字常量和符号常量
    文字常量又分为字符串常量和数值常量。

    Java常量使用中如何避免反模式?

     

    Java开发中,我们都会遇到各种各样的常量,常量的运用看似简单,其实里面还是有很多学问的,今天小编就将为大家分享在网上看到的一个Java常量避免反模式的相关文章,希望对大家在Java常量的使用中有帮助。

    在Java语言中,很多地方都需要各种各样的常量,所以常常就需要一个常量文件,来存储被多个地方引用的共享常量,但这个时候是应该用接口还是类呢?我们一起来看看吧。

    1、假设使用接口:

    package one;

    public interface Constants {

    String NAME="name1";

    int MAX_VAL=25;

    }

    package two;

    public class Constants {

    public static final String NAME="name1";

    public static final int MAX_VAL=25;

    }

     

    我更推荐的是使用接口。因为接口会自动将成员变量设置为静态的(static)、不可变的(final),这一点可以防止某些情况下错误地添加新的常量。这也使得代码看起来更简单和清晰。

    同时,一个的简单测试显示,同样的接口(字节码文件)占用的空间是209个字节(ubuntu 14.04机器上),而类(字节码文件)占用的空间是366个字节(同样的操作系统)。更少的字节码文件意味着加载和维护的成本更低。此外,JVM 加载接口的时候,不需要担心类提供的额外特征(如重载、方法的动态绑定等),因此加载更快。

     

     

    这是一个很典型的反模式例子,看起来非常好,但这也给应用后期的扩展留下了一个漏洞。

    假设存在在一个类,紧密依赖于这些常量。开发者在该类中写满了通过接口对常量的引用。如:

    packagename.Constant.CONSTANT_NAME

    所以,为了“清理”这段代码,他可能想实现该接口,这样他就不需要到处写“packagename.Constants”,所有的常量可以直接访问。

    但是,一旦实现了该接口,所有的常量就都变成“契约”(因为所有的常量都是公共的、静态的)的一部分。这导致为这个类增加了不必要的常量。这会动摇整个基础,并引起混乱。Java 中没有一种方式可以阻止类实现接口。

     

    而使用类的话,我们可以将类设置为final,这样就不能扩展。甚至,我们可以将构造器设置为私有的,以防止对这个类实例化,这样就永远不会破坏约定。此外,如果一个特殊的常量在同一个类中被多次使用,则开发者可以使用静态引入。

         对于所有的常量类,我们还可以这样设计:

    package three;

    //make the class non-extendable by adding final 增加final关键字来避免继承

    public final class Constants {

    //Hide the constructor 隐藏构造器

    private Constants(){}

    public static String NAME="name";

    }

     

    举个静态引入的例子:

    import static three.Constants.NAME;

    public class UseConstants {

      public static void main(String[] args) {

          System.out.println("the value of constants is"+NAME);

      }

    }

    这个设计问题也称为接口常量反模式(Constant Interface Anti-pattern)。

     

     

  • 相关阅读:
    小程序解析html(使用wxParse)
    error: the HTTP rewrite module requires the PCRE library
    CMake Error at cmake/boost.cmake:76 (MESSAGE)
    Centos7安装mysql后无法启动,提示 Unit mysql.service failed to load:No such file or directory
    mysql的三种安装方式
    epel源
    cmake
    yum
    wget
    tar指令
  • 原文地址:https://www.cnblogs.com/WuXuanKun/p/5578186.html
Copyright © 2020-2023  润新知