阅读开源框架总结Java类的定义

栏目: Java · 发布时间: 7年前

内容简介:Java的类是自定义的引用类型,是对在Presto框架中定义的这样包含数据或状态的对象通常会作为参数在方法调用之间传递,体现了诸如配置、视图模型、服务传输数据、协议数据等概念。除此之外,我们应尽量避免定义这样的对象去体现某种业务概念,因为基于

Java的类是自定义的引用类型,是对 职责相关 的行为与数据的一种封装,用以表现一种业务领域或者技术领域的概念。在不同的场景,类包含的成员可能有所不同,大体可以分为如下五类:

  • 数据类:可以视为是持有数据的容器,类的成员只包含了字段,以及与字段有关的get/set方法
  • 实体类:既包含了体现状态的字段,又包含了操作这些状态的方法
  • 服务类:只有方法(行为)没有字段(状态),可以理解为提供内聚职责的服务
  • 函数类:如果定义的公开方法只有唯一一个,可以理解为它封装的其实是一个函数,通常用匿名类或者Lambda表示
  • 工具类:只包含一系列静态方法,通常不支持对该类型的实例化

数据类

在Presto框架中定义的 ClientSession 可以认为是这样一种数据类。除了构造函数外,它只定义了字段与对应的 get() 方法(实际上,在框架的源代码中,在 ClientSession 类中还定义了一系列静态工厂方法,但本质上说, ClientSession 还是一个数据类),用以持有客户端Session所必须的数据:

public class ClientSession
{
    private final URI server;
    private final String user;
    private final String source;
    private final String clientInfo;
    private final String catalog;
    private final String schema;
    private final TimeZoneKey timeZone;
    private final Locale locale;
    private final Map<String, String> properties;
    private final Map<String, String> preparedStatements;
    private final String transactionId;
    private final boolean debug;
    private final Duration clientRequestTimeout;

    public ClientSession(
            URI server,
            String user,
            String source,
            String clientInfo,
            String catalog,
            String schema,
            String timeZoneId,
            Locale locale,
            Map<String, String> properties,
            String transactionId,
            boolean debug,
            Duration clientRequestTimeout)
    {
        this(server, user, source, clientInfo, catalog, schema, timeZoneId, locale, properties, emptyMap(), transactionId, debug, clientRequestTimeout);
    }

    public ClientSession(
            URI server,
            String user,
            String source,
            String clientInfo,
            String catalog,
            String schema,
            String timeZoneId,
            Locale locale,
            Map<String, String> properties,
            Map<String, String> preparedStatements,
            String transactionId,
            boolean debug,
            Duration clientRequestTimeout)
    {
        this.server = requireNonNull(server, "server is null");
        this.user = user;
        this.source = source;
        this.clientInfo = clientInfo;
        this.catalog = catalog;
        this.schema = schema;
        this.locale = locale;
        this.timeZone = TimeZoneKey.getTimeZoneKey(timeZoneId);
        this.transactionId = transactionId;
        this.debug = debug;
        this.properties = ImmutableMap.copyOf(requireNonNull(properties, "properties is null"));
        this.preparedStatements = ImmutableMap.copyOf(requireNonNull(preparedStatements, "preparedStatements is null"));
        this.clientRequestTimeout = clientRequestTimeout;

        // verify the properties are valid
        CharsetEncoder charsetEncoder = US_ASCII.newEncoder();
        for (Entry<String, String> entry : properties.entrySet()) {
            checkArgument(!entry.getKey().isEmpty(), "Session property name is empty");
            checkArgument(entry.getKey().indexOf('=') < 0, "Session property name must not contain '=': %s", entry.getKey());
            checkArgument(charsetEncoder.canEncode(entry.getKey()), "Session property name is not US_ASCII: %s", entry.getKey());
            checkArgument(charsetEncoder.canEncode(entry.getValue()), "Session property value is not US_ASCII: %s", entry.getValue());
        }
    }

    public URI getServer()
    {
        return server;
    }

    public String getUser()
    {
        return user;
    }

    public String getSource()
    {
        return source;
    }

    public String getClientInfo()
    {
        return clientInfo;
    }

    public String getCatalog()
    {
        return catalog;
    }

    public String getSchema()
    {
        return schema;
    }

    public TimeZoneKey getTimeZone()
    {
        return timeZone;
    }

    public Locale getLocale()
    {
        return locale;
    }

    public Map<String, String> getProperties()
    {
        return properties;
    }

    public Map<String, String> getPreparedStatements()
    {
        return preparedStatements;
    }

    public String getTransactionId()
    {
        return transactionId;
    }

    public boolean isDebug()
    {
        return debug;
    }

    public Duration getClientRequestTimeout()
    {
        return clientRequestTimeout;
    }

    @Override
    public String toString()
    {
        return toStringHelper(this)
                .add("server", server)
                .add("user", user)
                .add("clientInfo", clientInfo)
                .add("catalog", catalog)
                .add("schema", schema)
                .add("timeZone", timeZone)
                .add("locale", locale)
                .add("properties", properties)
                .add("transactionId", transactionId)
                .add("debug", debug)
                .toString();
    }
}

这样包含数据或状态的对象通常会作为参数在方法调用之间传递,体现了诸如配置、视图模型、服务传输数据、协议数据等概念。除此之外,我们应尽量避免定义这样的对象去体现某种业务概念,因为基于 “信息专家”模式 ,好的面向对象设计应该是将数据与操作这些数据的行为封装在一起。

实体类

这是最为常见的一种类定义,也是符合面向对象设计原则的,前提是定义的类必须是高内聚的,原则上应该满足单一职责原则。例如JDK定义的 Vector 展现了一种数据结构,因而它持有的字段与方法应该仅仅与队列操作与状态有关:

public class Vector<E>
    extends AbstractList<E>
    implements List<E>, RandomAccess, Cloneable, java.io.Serializable
{
  
    protected Object[] elementData;
    protected int elementCount;
    protected int capacityIncrement;

    public Vector(int initialCapacity, int capacityIncrement) {
        super();
        if (initialCapacity < 0)
            throw new IllegalArgumentException("Illegal Capacity: "+
                                              initialCapacity);
        this.elementData = new Object[initialCapacity];
        this.capacityIncrement = capacityIncrement;
    }

  public Vector(int initialCapacity) {
        this(initialCapacity, 0);
    }

  public synchronized void setSize(int newSize) {
        modCount++;
        if (newSize > elementCount) {
            ensureCapacityHelper(newSize);
        } else {
            for (int i = newSize ; i < elementCount ; i++) {
                elementData[i] = null;
            }
        }
        elementCount = newSize;
    }

    public synchronized int size() {
        return elementCount;
    }

    public synchronized boolean isEmpty() {
        return elementCount == 0;
    }

    public boolean contains(Object o) {
        return indexOf(o, 0) >= 0;
    }

    public synchronized E firstElement() {
        if (elementCount == 0) {
            throw new NoSuchElementException();
        }
        return elementData(0);
    }

    public synchronized void insertElementAt(E obj, int index) {
        modCount++;
        if (index > elementCount) {
            throw new ArrayIndexOutOfBoundsException(index
                                                    + " > " + elementCount);
        }
        ensureCapacityHelper(elementCount + 1);
        System.arraycopy(elementData, index, elementData, index + 1, elementCount - index);
        elementData[index] = obj;
        elementCount++;
    }

    public synchronized void addElement(E obj) {
        modCount++;
        ensureCapacityHelper(elementCount + 1);
        elementData[elementCount++] = obj;
    }

    public synchronized boolean removeElement(Object obj) {
        modCount++;
        int i = indexOf(obj);
        if (i >= 0) {
            removeElementAt(i);
            return true;
        }
        return false;
    }

    public synchronized void removeAllElements() {
        modCount++;
        // Let gc do its work
        for (int i = 0; i < elementCount; i++)
            elementData[i] = null;

        elementCount = 0;
    }
}

如下类的定义则体现了一种业务概念,方法 changePriceTo() 实际上表现的是一种业务规则,而它要操作的数据就是 Product 类自身持有的字段 sellingPrice

public class Product extends Entity<Identity> {
    private final List<Option> options;
    private Price sellingPrice;
    private Price retailPrice;

    public Product(Identity id, Price sellingPrice, Price retailPrice)  {
        super(id);
        this.sellingPrice = sellingPrice;
        if (!sellingPriceMatches(retailPrice) {
            throw new PricesNotInTheSameCurrencyException("Selling and retail price must be in the same currency");
        }
        this.retailPrice = retailPrice;
        options = new List<Option>();
    }

    public void changePriceTo(Price newPrice) {
        if (!sellingPriceMatches(newPrice)) {
            throw new PricesNotInTheSameCurrencyException("You cannot change the price of this product to a different currency");
        }
        sellingPrice = newPrice;
    }

    public Price savings() {
        Price savings = retailPrice.minus(sellingPrice);
        if (savings.isGreaterThanZero()) 
            return savings;
        else
            return new Price(0m, sellingPrice.currency);
    }

    private bool sellingPriceMatches(Price retailPrice) {
        return sellingPrice.sameCurrency(retailPrice); 
    }

    public void add(Option option) {
        if (!this.contains(option)) 
            options.Add(option);
        else
            throw new ProductOptionAddedNotUniqueException(string.Format("This product already has the option {0}", option.ToString()));
    }

    public bool contains(Option option) {
        return options.Contains(option); 
    }
}

服务类

只有方法没有状态的类定义是对行为的封装,行为的实现要么是通过操作内部封装的不可变私有数据,要么是通过操作传入的参数对象实现对状态的修改。由于参数传入的状态与服务类自身没有任何关系,因此这样的类通常也被视为无状态的类。以下代码是针对升级激活包的验证服务:

public class PreActivePackageValidator {
    public long validatePreActivePackage(ActiveManifest  activeManifest) {
          validateSamePackageType(activeManifest);
          validateNoTempPackage(activeManifest);
          validateNoPackageRunning(activeManifest);
          validateAllPackagesBeenDownloaded(activeManifest);
          validateNoFatherPackageBakStatus(activeManifest);
          validatePackageNum(activeManifest);
    }
    private void validateSamePackageType(ActiveManifest  activeManifest) {
        int packakeType = activeManifest.getPackageType();
        for (UpagrdePackage pkg : activeManifest.getPackages()) {
            if (packageType != pkg.getPackageType()) {
                throw new PackagePreActiveException("pre active exist different type package");
            }
        }
    }
}

服务类还可以操作外部资源,例如读取文件、访问数据库、与第三方服务通信等。例如airlift框架定义的 ConfigurationLoader 类,就提供加载配置文件内容的服务:

public class ConfigurationLoader {
    public Map<String, String> loadProperties()
            throws IOException    {
        Map<String, String> result = new TreeMap<>();
        String configFile = System.getProperty("config");
        if (configFile != null) {
            result.putAll(loadPropertiesFrom(configFile));
        }

        result.putAll(getSystemProperties());

        return ImmutableSortedMap.copyOf(result);
    }

    public Map<String, String> loadPropertiesFrom(String path)
            throws IOException    {
        Properties properties = new Properties();
        try (Reader reader = new FileReader(new File(path))) {
            properties.load(reader);
        }

        return fromProperties(properties);
    }

    public Map<String, String> getSystemProperties()    {
        return fromProperties(System.getProperties());
    }
}

函数类

可以将函数类理解为设计一个类,它仅仅实现了一个接口,且该接口只定义一个方法。使用时,我们会基于 依赖倒置原则(DIP) 从接口的角度使用这个类。为了重用的目的,这个类可以单独被定义,也可能体现为匿名类,或者 Java 8中的Lambda表达式。

单独类形式

例如,在Presto中定义了 PagesIndexComparator 接口,提供了比较方法以用于支持对页面索引的排序。接口的定义为:

public interface PagesIndexComparator {
    int compareTo(PagesIndex pagesIndex, int leftPosition, int rightPosition);
}

Presto定义了该接口的实现类 SimplePagesIndexComparator ,该类就是一个函数类:

public class SimplePagesIndexComparator
        implements PagesIndexComparator {
    private final List<Integer> sortChannels;
    private final List<SortOrder> sortOrders;
    private final List<Type> sortTypes;

    public SimplePagesIndexComparator(List<Type> sortTypes, List<Integer> sortChannels, List<SortOrder> sortOrders)   {
        this.sortTypes = ImmutableList.copyOf(requireNonNull(sortTypes, "sortTypes is null"));
        this.sortChannels = ImmutableList.copyOf(requireNonNull(sortChannels, "sortChannels is null"));
        this.sortOrders = ImmutableList.copyOf(requireNonNull(sortOrders, "sortOrders is null"));
    }

    @Override
    public int compareTo(PagesIndex pagesIndex, int leftPosition, int rightPosition)   {
        long leftPageAddress = pagesIndex.getValueAddresses().getLong(leftPosition);
        int leftBlockIndex = decodeSliceIndex(leftPageAddress);
        int leftBlockPosition = decodePosition(leftPageAddress);

        long rightPageAddress = pagesIndex.getValueAddresses().getLong(rightPosition);
        int rightBlockIndex = decodeSliceIndex(rightPageAddress);
        int rightBlockPosition = decodePosition(rightPageAddress);

        for (int i = 0; i < sortChannels.size(); i++) {
            int sortChannel = sortChannels.get(i);
            Block leftBlock = pagesIndex.getChannel(sortChannel).get(leftBlockIndex);
            Block rightBlock = pagesIndex.getChannel(sortChannel).get(rightBlockIndex);

            SortOrder sortOrder = sortOrders.get(i);
            int compare = sortOrder.compareBlockValue(sortTypes.get(i), leftBlock, leftBlockPosition, rightBlock, rightBlockPosition);
            if (compare != 0) {
                return compare;
            }
        }
        return 0;
    }
}

我们看到 SimplePagesIndexComparator 类的逻辑相对比较复杂,构造函数也需要传入三个参数: List<Type> sortTypesList<Integer> sortChannelsList<SortOrder> sortOrders 。虽然从接口的角度看,其实代表的是compare的语义,但由于逻辑复杂,而且需要传入三个对象帮助对 PagesIndex 进行比较,因而不可能实现为匿名类或者Lambda表达式。在Presto中,对它的使用为:

public class PagesIndexOrdering {
    private final PagesIndexComparator comparator;

    public PagesIndexOrdering(PagesIndexComparator comparator)  {
        this.comparator = requireNonNull(comparator, "comparator is null");
    }

    public PagesIndexComparator getComparator()  {
        return comparator;
    }

    /**
    * Returns the index of the median of the three positions.
    */
    private int median3(PagesIndex pagesIndex, int a, int b, int c)    {
        int ab = comparator.compareTo(pagesIndex, a, b);
        int ac = comparator.compareTo(pagesIndex, a, c);
        int bc = comparator.compareTo(pagesIndex, b, c);
        return (ab < 0 ?
                (bc < 0 ? b : ac < 0 ? c : a) :
                (bc > 0 ? b : ac > 0 ? c : a));
    }
}

匿名类形式

同样在该框架下定义的 IntComparator 接口,它的实现就完全不同了。首先是该接口的定义:

public interface IntComparator {
    /** Compares the given primitive types.
    *
    * @see java.util.Comparator
    * @return A positive integer, zero, or a negative integer if the first
    * argument is greater than, equal to, or smaller than, respectively, the
    * second one.
    */
    int compare(int k1, int k2);
}

在针对整型数据提供 排序 功能时,用到了 IntComparator 接口:

public final class IntBigArray {
    public void sort(int from, int to, IntComparator comparator)    {
        IntBigArrays.quickSort(array, from, to, comparator);
    }
}

但由于提供整型数据的比较逻辑相对简单,在Presto中并没有定义显式的函数类,而是使用了Lambda表达式:

groupIds.sort(0, groupByHash.getGroupCount(), (leftGroupId, rightGroupId) ->
                Long.compare(groupByHash.getRawHash(leftGroupId), groupByHash.getRawHash(rightGroupId)));

这里的Lambda表达式其实也可以理解为是一个函数类。

函数重用形式

还有一种特殊的函数类,它的定义形式与后面介绍的 工具 类非常相似,同样是定义了一组静态方法,但它的目的不是提供工具或辅助功能,而是将其视为函数成为被重用的单元。这时,需要用到Java 8提供的方法引用(method reference)语法。例如我们要对 List<Apple> 集合进行过滤,过滤条件分别为颜色与重量,这时可以在 Apple 类中定义两个静态方法:

public class Apple {
    public static boolean isGreenApple(Apple apple) {
        return "green".equals(apple.getColor());
    }

    public static boolean isHeavyApple(Apple apple) {
        return apple.getWeight() > 150;
    }
}

这两个方法实际上满足函数接口 Predicate<Apple> 的定义,因此可以在 filter 方法中传入这两个方法的引用:

public List<Apple> filter(Predicate<Apple> predicate) {
    ArrayList<Apple> result = new ArrayList<>();
    for (Apple apple : apples) {
        if (predicate.test(apple)) {
            result.add(apple);
        }
    }
    return result;
}

public List<Apple> filterGreenApples() {
    return filter(Apple::isGreenApple);
}

public List<Apple> filterHeavyApples() {
    return filter(Apple::isHeavyApple);
}

此时 Apple 类可以认为是一个函数类,但准确地说法是一系列可以被重用的函数的容器。与工具类不同的是,这些函数并不是被直接调用,本质上讲,其实是作为“高阶函数”被传递给其他方法而被重用。虽然说实例方法也可以采用这种方式而被重用,但静态方法的调用会更加简单。

工具类

在许多项目或开源项目中,随处可见工具类的身影。无需实例化的特性使得我们使用工具类的方式时变得非常的便利,也不需要考虑状态的维护。然而越是方便,我们越是要警惕工具类的陷阱——设计出臃肿庞大无所不能的上帝工具类。工具类仍然要遵循高内聚的原则,只有强相关的职责才能放到同一个工具类中。

在定义工具类时,通常有三类命名范式:

  • 名词复数形式:工具类其实就是一系列工具方法的容器,当我们要针对某种类型(或对象)提供工具方法时,可以直接将工具类命名为该类型的复数形式,例如操作 Collection 的工具类可以命名为 Collections ,操作 Object 的工具类可以命名为 Objects ,而与前置条件有关的工具类则被命名为 Preconditions
  • 以Util为后缀:这体现了工具(Utility)的语义,当我们在类名中看到 Util 后缀时,就可以直观地了解到这是一个工具类。例如 ArrayUtil 类是针对数组的工具类, DatabaseUtil 是针对数据库操作的工具类, UuidUtil 是针对Uuid的工具类。
  • 以Helper为后缀:这种命名相对较少,但许多框架也采用这种命名方式来体现“辅助类”的含义。例如在Druid框架中,就定义了 JobHelperGroupByQueryHelper 等辅助类。

工具类是无需实例化的,因此在定义工具类时,尽可能将其声明为final类,并为其定义私有的构造函数。例如Guava框架提供的 Preconditions 工具类:

public final class Preconditions {
    private Preconditions() {
    }

    public static void checkArgument(boolean expression) {
        if(!expression) {
            throw new IllegalArgumentException();
        }
    }
    
    //other util methods
}

以上就是本文的全部内容,希望本文的内容对大家的学习或者工作能带来一定的帮助,也希望大家多多支持 码农网

查看所有标签

猜你喜欢:

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

The Seasoned Schemer

The Seasoned Schemer

Daniel P. Friedman、Matthias Felleisen / The MIT Press / 1995-12-21 / USD 38.00

drawings by Duane Bibbyforeword and afterword by Guy L. Steele Jr.The notion that "thinking about computing is one of the most exciting things the human mind can do" sets both The Little Schemer (form......一起来看看 《The Seasoned Schemer》 这本书的介绍吧!

CSS 压缩/解压工具
CSS 压缩/解压工具

在线压缩/解压 CSS 代码

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

RGB HEX 互转工具

Base64 编码/解码
Base64 编码/解码

Base64 编码/解码