简体   繁体   English

使用单例模式对 Java 中的大型泛型类有好处吗?

[英]Is using a Singleton pattern good for large Generic classes in Java?

I'm new to Generics and the Singleton pattern in Java.我是 Java 中泛型和单例模式的新手。 I have the following questions:我有以下问题:

  • Is a Generic class compatible with the Singleton Pattern, namely having a static instance?泛型类是否与单例模式兼容,即具有静态实例?

  • If so, I have the following Generic class that I want to convert to a Singleton.如果是这样,我有以下通用类要转换为单例。 What is the best practice?最佳做法是什么?

  • (Beginner question) Is there a good way to de-generify an instance of the class in runtime? (初学者问题)有没有一种在运行时对类的实例进行去泛化的好方法? passing the class as parameter in getInstance()?在 getInstance() 中将类作为参数传递?

class GenFooSingleton <T>
{
    private T t;

    // Prevent direct instantiation
    private GenFooSingleton(T o){
        this.t = o;
    }

    private static GenFooSingleton INSTANCE = null;

    // Returns the single instance of this class, creating it if necessary
    static synchronized <T> GenFooSingleton<T> getInstance(T tClass)
    {
        if (INSTANCE == null)
        {
            INSTANCE = new GenFooSingleton<>(tClass);
        }
        return INSTANCE;
    }

}

Edit编辑

My use case of a Generic with Singleton:我的单例泛型用例:

1. Why Generic? 1. 为什么是泛型? First let's say I have the the following Singleton Repository for one type of data to begin with, the following example is from what I learned in the googlesamples/android-architecture首先,假设我有以下单例存储库用于一种类型的数据,以下示例来自我在googlesamples/android-architecture中学到的内容

class FooRepository implements FooDatasource
{

    private final FooDatasource local;
    private final FooDatasource remote;
    Map<String, Foo> mCahcedItems;

    // Prevent direct instantiation
    private FooRepository(FooDatasource remote, FooDatasource local){
        this.remote = remote;
        this.local = local;
    }

    private static FooRepository INSTANCE = null;

    // Returns the single instance of this class, creating it if necessary
    public static synchronized FooRepository getInstance(FooDatasource remote, FooDatasource local)
    {
        if (INSTANCE == null)
        {
            new FooRepository(remote,local);
        }
        return INSTANCE;
    }

    // implement CRUD methods
    @Override
    public Flowable<List<Foo>> getFoos(){
        // Update the mCahcedItems with the list of Foos
        // return a list of Foos and syncing between the local and remote datasources...For brevity the bunch of Rxjava implementation is omitted.  
    }

    @Override
    public Flowable<Optional<Foo>> getFoo(){
        // Update the mCahcedItems with Foo
        //...
    }
}

But I can see I would have to create repository for each data type.但是我可以看到我必须为每种数据类型创建存储库。 (Foo, Baa, Daa, etc) where the CURD logic is essentially the same and each instance. (Foo、Baa、Daa 等)其中 CURD 逻辑本质上是相同的,并且每个实例都是一样的。 So naturally I'm thinking of making the repository a Generic one.所以很自然地,我正在考虑使存储库成为通用存储库。

2. Why Singleton? 2. 为什么是单例? Without using the Singleton pattern, each new instance would start a complete new reload the in-memory cache, which means new local database queries.如果不使用单例模式,每个新实例都会启动一个全新的内存缓存重新加载,这意味着新的本地数据库查询。 In developing for mobile and memory constrained devices (Android Apps), that would amount to unnecessary and I/O calls each time the device changes configuration/rotation.在为移动设备和内存受限设备(Android 应用程序)进行开发时,每次设备更改配置/旋转时都会产生不必要的 I/O 调用。 The mere thought of that just flags a huge performance problem that I would have to deal with.仅仅想到这一点就表明我必须处理一个巨大的性能问题。 Therefore, I think A globally accessible single instance that is only lazily instantiated is a plus.因此,我认为仅延迟实例化的全局可访问单个实例是一个加分项。

My attempt So I set out to create generic versions of both the Repository and Datasource interface, and have each data type provide the concrete implementation when they implement the Datasource interface, like below:我的尝试所以我着手创建 Repository 和 Datasource 接口的通用版本,并让每种数据类型在实现 Datasource 接口时提供具体的实现,如下所示:

class FooDatasource implements GenericDatasource<Foo>
{
    //...
}

class BarDatasource implements GenericDatasource<Bar>
{
    //...and so on and so forth
}

Update更新

My current approach is a singleton pattern with Generic instances can be better managed with Dependency injection using Dagger 2, for both Java and specifically Android dev.我目前的方法是使用 Dagger 2 通过依赖注入更好地管理通用实例的单例模式,适用于 Java 和特别是 Android 开发。

Is a Generic class compatible with the Singleton Pattern, namely having a static instance?泛型类是否与单例模式兼容,即具有静态实例?

No, it won't work like that, static fields exist only once, static inheritance doesn't exist in Java.不,它不会那样工作,静态字段只存在一次,Java 中不存在静态继承。 But there are many different ways to implement a singleton.但是有许多不同的方法来实现单例。

If so, I have the following Generic class that I want to convert to a Singleton.如果是这样,我有以下通用类要转换为单例。 What is the best practice?最佳做法是什么?

Don't do it.不要这样做。 Singleton is an antipattern, mainly because it's horrible for testing purposes. Singleton 是一种反模式,主要是因为它对于测试目的来说很糟糕。 Instead, use a container (Spring, Guice, EJB etc.) to manage singletons for you, making sure only one instance exists.相反,使用容器(Spring、Guice、EJB 等)为您管理单例,确保只有一个实例存在。 Start by reading about the Dependency Inversion Principle and Inversion of Control .首先阅读依赖倒置原则控制倒置

(Beginner question) Is there a good way to de-generify an instance of the class in runtime? (初学者问题)有没有一种在运行时对类的实例进行去泛化的好方法? passing the class as parameter in getInstance()?在 getInstance() 中将类作为参数传递?

Yes, passing the class to getInstance would actually make this a bit nicer, especially if you use a class-to-instance-Map internally ( Guava has such a type )是的,将类传递给 getInstance 实际上会使这更好一点,特别是如果您在内部使用类到实例映射( 番石榴有这样的类型

  1. Since the singleton pattern tries to guarantee only one living instance of a given class at any time, it's not very compatible with the idea of a versatile class that can accept or produce different results depending on its current generic type.由于单例模式在任何时候都试图保证给定类的一个活实例,因此它与通用类的想法不太兼容,后者可以根据当前的泛型类型接受或产生不同的结果。 For generics to be any useful, you need to be able to create different flavors of the same type (usual exemple: a List of String and a List of Integer)为了使泛型有用,您需要能够创建相同类型的不同风格(通常的例子:字符串列表和整数列表)
  2. N/A不适用
  3. If you are passing a parameter to the getInstance of a singleton, then you are not really wanting a singleton but a factory.如果您将参数传递给单例的 getInstance,那么您并不是真正想要单例而是工厂。 A singleton can only be non parameterized, or else the first call freezes the context.单例只能是非参数化的,否则第一次调用会冻结上下文。

Do not abuse of singleton.不要滥用单身人士。 They are the first pattern you may try to implement, because it's the first in every book, but it's almost always at least useless, at most a performance bottleneck and a bad design decision (not very OO)它们是您可能尝试实现的第一个模式,因为它是每本书中的第一个,但它几乎总是至少没用,最多是性能瓶颈和糟糕的设计决策(不是很面向对象)

EDIT:编辑:

you assumption that each new instance couldn't share the same cache is basically wrong, for two reasons:您假设每个新实例不能共享相同的缓存基本上是错误的,原因有两个:

  1. not using a Singleton does not forces you to use several instances of the same type.不使用 Singleton 不会强制您使用多个相同类型的实例。 It just allows you to do so, as well as enabling inheritance (which the singleton simply cann't).它只是允许您这样做,以及启用继承(单例根本不能)。 If you use Spring and a singleton-scoped bean (the default), then your repository exists only once in memory - even if it does not implement the singleton pattern as described in the books - and is shared between all consummers (thus only one cache).如果你使用 Spring 和一个单例范围的 bean(默认),那么你的存储库在内存中只存在一次——即使它没有实现书中描述的单例模式——并且在所有消费者之间共享(因此只有一个缓存)。 This can be accomplished without spring too, just use some kind of factory or registry.这也可以在没有 spring 的情况下完成,只需使用某种工厂或注册表。
  2. caching with an hashmap in your class is a bit fishy, too.在你的班级中使用 hashmap 缓存也有点可疑。 Caching is an implementation detail and you should not try to implement it in this way (you will end up eating the whole memory quite easily, at least, use WeakHashMap instead - or the Guava version, using CacheBuilder).缓存是一个实现细节,你不应该尝试以这种方式实现它(你最终会很容易吃掉整个内存,至少,使用 Wea​​kHashMap 代替 - 或者使用 CacheBuilder 的 Guava 版本)。 You could also declare your cache as static so it will only exist once in memory.您还可以将缓存声明为静态缓存,这样它在内存中只会存在一次。 Modern applications treat caching as an aspect , like transactions for example.现代应用程序将缓存视为一个方面,例如事务。 It should not leak to your code.它不应该泄漏到您的代码中。 For example, look at ehcache, redis, terracotta, etc. they all implement the JSR-107 and are configured directly on your method prototype, with some annotation (@Cacheable, etc.).比如看ehcache、redis、terracotta等,它们都实现了JSR-107,直接在你的方法原型上配置,加上一些注解(@Cacheable等)。 Ho and caching usually goes to the service layer - you do not cache the state of the db, you cache the responses sent to the users after processing of the business logic (even if this rule is not absolutely strict) Ho 和缓存通常进入服务层——你不缓存数据库的状态,你缓存在处理业务逻辑后发送给用户的响应(即使这个规则不是绝对严格的)

The singleton also has a very big problem: it is directly responsible to instantiate the object, that is, direct use of the new keyword.单例还有一个非常大的问题:它直接负责实例化对象,也就是直接使用new关键字。 This is a pain, as you can not change the concret type of the implementation at runtime (for testing purpose, or any other use).这很痛苦,因为您无法在运行时更改实现的具体类型(出于测试目的或任何其他用途)。 Look at the factory/factory method pattern to see a better way to change the type at runtime.查看工厂/工厂方法模式以了解在运行时更改类型的更好方法。

What you can do is having an abstract base class, generified, that your concret dao will extend (but those will not be generic).你可以做的是有一个抽象基类,泛型,你的具体 dao 将扩展(但那些不会是通用的)。 Something like this:像这样的东西:

abstract class AbstractDao<ID, T> {
  private Class type;
  protected AbstractDao(Class type) {
      this.type = type;
  }
  void save(T entity) {
    // save an entity
  }

  T get(ID pkey) { /* get an entity */}
  ...
}

public class DaoX extends AbstractDao<Long, X> {
  DaoX() {
    super(X.class)
  }
  /* Empty! or only methods applicable for X */
}
public class DaoY extends AbstractDao<Integer, Y> {
  DaoY() {
    super(Y.class)
  }
  /* Empty! or only methods applicable for Y */
}

In this case you are not duplicating any code.在这种情况下,您不会复制任何代码。

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM