【问题标题】:Instanciation depending on type, generics, inheritance or both?取决于类型、泛型、继承或两者的实例化?
【发布时间】:2018-05-15 15:26:55
【问题描述】:

我有一个方法实例化一个特定的类,具体取决于类型发送作为该方法的参数。这工作得很好,但我真的不喜欢if(c == class_name.class) 处理这个问题的方式,我很确定使用泛型和继承有更好的方法来做到这一点。我使用的方法看起来(可能是错误的......)对我来说很脏。

我遵循了几个关于泛型和继承的教程,但即使我认为我或多或少地分别理解这两个概念,我也在努力理解两者的混合......而且我认为这两个概念一旦混合就可以解决我的问题。任何人都可以告诉我正确的轨道,泛型,继承,或者让我的代码保持这样。有没有更好的方法来做到这一点?

这是我的实际方法:

private void addData(String csvFile, char separator, Class<?> c) {
        int lineNumber = 1;
        CSVReader reader;

        try {
            reader = new CSVReader(new InputStreamReader(getAssets().open(csvFile)), separator);
            String[] line;

            realm.beginTransaction();
            if(c == FlagDef.class) {
                while ((line = reader.readNext()) != null) {
                    FlagDef flagDef = new FlagDef(Long.parseLong(line[0]), line[1], line[2]);
                    Log.d(TAG, String.format("%s %s %s", line[0], line[1], line[2]));
                    realm.copyToRealm(flagDef);
                    lineNumber++;
                }
            }
            if(c == Picture.class) {
                while ((line = reader.readNext()) != null) {
                    Picture picture = new Picture(Long.parseLong(line[0]), line[1]);
                    Log.d(TAG, String.format("%s %s", line[0], line[1]));
                    realm.copyToRealm(picture);
                    lineNumber++;
                }
            }
            realm.commitTransaction();

            reader.close();
        } catch (FileNotFoundException ex) {
            Log.e(TAG, String.format("File %s not found : %s", csvFile, ex));
        } catch (IOException ex) {
            Log.e(TAG, String.format("Error parsing line number %s : %s", lineNumber, ex));
        }
    }

对该方法的调用:

addData(FILE_FLAGS,SEPARATOR,FlagDef.class);
addData(FILE_PICTURES,SEPARATOR,Picture.class);

copyToRealm方法的声明可见here

这是针对 Android 的,我实际上是针对 API 16。

为了简洁起见,我在这里只指出PictureFlagDef 两种类型,但我计划至少有10 种不同的类型。

【问题讨论】:

  • 这听起来不可能,你的类型(类)在运行时是已知的,那时没有类型 T (或任何你有的泛型)。传递Class&lt;T&gt; 在这里似乎是正确的,对我来说一点也不脏
  • 在您的情况下,我认为没有其他解决方案可以避免if(c == class_name.class),因为您无法使用 CSV 文件确定对象的类型。但是,您的问题看起来像 Strategy Design Pattern IMO 问题。
  • 调用addData时如何获取c的值?
  • @StephaneM 我刚刚编辑了我的问题。这里的上下文是我只有几个 csv 文件,其中包含需要放入 Realm 数据库的数据。所以我知道要发送哪个类作为参数,因为每个文件对应于映射到我的 Realm 数据库对象的特定类。
  • 泛型在这里帮不了你。它们是仅编译时的构造。

标签: java android generics inheritance


【解决方案1】:

有类型擦除的概念,在运行时,泛型将被擦除。您现在拥有的东西被认为是“干净的”,并且在这种情况下使用了很多。

我唯一能建议的就是让你的PictureFlagDef 可以实现一个标记接口,例如CSVable(是的,真糟糕的名字)并将你的方法更改为:

<T extends CSV> void addData(Class<T> c)

以便在编译时过滤您的类型(您只能传递实现 CSVable 的对象)

【讨论】:

  • 感谢您的回答。我喜欢这个。仍然只是犹豫要不要像这样添加这个标记界面或选择 StephaneM 答案。
  • @Laurent 如果您打算拥有两种以上的类型,则策略模式更正确,尤其是,如果您可能在多种情况下使用重复的代码。
  • 谢谢@Eugene,我正在看这个Strategy Pattern。我实际上计划拥有超过 10 种不同的类型。
【解决方案2】:

我认为这是一个模块化问题,为什么不创建两个添加数据的方法并避免类类型参数?

private void addFlagData(String csvFile, char separator) {
...
}

private void addPictureData(String csvFile, char separator) {
...
}

【讨论】:

  • 在这种情况下,最好使用cmets中的策略模式来解决问题
  • 感谢您的回答,但我不同意。为了简洁起见,我实际上只放了两个案例,标志和图片。但最后我将有超过 10 种方法。我不能编写 10 次相同的方法,这对我来说看起来仍然很脏 :) 甚至对于 2 种情况,我也不喜欢代码复制。
【解决方案3】:

您可以创建一个构建器类,该类将创建一个读取一行的对象:

abstract class Builder<T> {
    abstract T build( String[] line);
}

有 2 个实现:

class FlagDefBuilder extends Builder<FlagDef > {

    @Override
    FlagDef build( String[] line )
    {
        FlagDef flagDef = new FlagDef(Long.parseLong(line[0]), line[1], line[2]);
        Log.d(TAG, String.format("%s %s %s", line[0], line[1], line[2]));
        return flagDef;
    } 

}

    class PictureBuilder extends Builder<FlagDef > {

        @Override
        FlagDef build( String[] line )
        {
            Picture picture = new Picture(Long.parseLong(line[0]), line[1]);
            Log.d(TAG, String.format("%s %s", line[0], line[1]));
            return picture;
        }       
    }

那么addData会是这样的:

    private void addData(String csvFile, char separator, Builder<?> b) {
        int lineNumber = 1;
        CSVReader reader;

        try {
            reader = new CSVReader(new InputStreamReader(getAssets().open(csvFile)), separator);
            String[] line;

            realm.beginTransaction();
                while ((line = reader.readNext()) != null) {
                    realm.copyToRealm(b.build(line));
                    lineNumber++;
                }
            }
            realm.commitTransaction();

            reader.close();
        } catch (FileNotFoundException ex) {
            Log.e(TAG, String.format("File %s not found : %s", csvFile, ex));
        } catch (IOException ex) {
            Log.e(TAG, String.format("Error parsing line number %s : %s", lineNumber, ex));
        }
    }

你可以这样调用它:

addData(FILE_FLAGS,SEPARATOR, new FlagDefBuilder());
addData(FILE_PICTURES,SEPARATOR, new PictureBuilder());

您可以在不修改 addData 的情况下添加第三个构建器类型。

【讨论】:

  • 注意:我不知道为什么我创建了一个抽象类而不是一个接口。
  • 我喜欢你的方法。但我不确定采用它,因为有几个人告诉我我的解决方案并不脏。你的看起来不错,但最终它引入了更多的冗长和“复杂性”。但是是的,我喜欢它,我只是不确定是否想让我的代码更复杂,只是因为如果我的第一个代码是一个好的解决方案,它看起来更好看。无论如何,如果我最终选择它,我不会忘记接受你的答案,仍然不确定。谢谢。
  • 这仅在copyToRealm 参数的类型为Object 时才有效(不是)。对于这种情况,可以通过使addData 泛型并以RealmModel 为界来修复它,但这不是一个好的通用解决方案。
【解决方案4】:

将您的 Class&lt;?&gt; c 参数更改为 Function&lt;String[], RealmModel&gt;

private void addData(String csvFile, char separator, Function<String[], RealmModel> modelConstructor) {

除了所有那些if 块,你可以只拥有一个:

while ((line = reader.readNext()) != null) {
    RealmModel model = modelConstructor.apply(line);
    Log.d(TAG, String.join(" ", line));
    realm.copyToRealm(model);
    lineNumber++;
}

对 addData 方法的调用如下所示:

addData(FILE_FLAGS, SEPARATOR, line -> new FlagDef(Long.parseLong(line[0]), line[1], line[2]));
addData(FILE_PICTURES, SEPARATOR, line -> new Picture(Long.parseLong(line[0]), line[1]));

【讨论】:

  • 我喜欢这个!只是想完全理解它:)
  • 我必须承认这是一个非常聪明的解决方案!!!不幸的是,我应该在我的问题中提供更多细节。这是针对 Android 的,Function 仅在 API 24 之后添加。我无法针对 API 24,因为它只是 20% market devices...但再次感谢您。
【解决方案5】:

我的问题有几个答案。

我认为@VGR 的解决方案是一个很好的解决方案。但问题在于我的项目针对的是Android API 16。所以在我的情况下,@VGR 提出的功能解决方案无法使用,因为Function was only introduced since API 24。因此,该解决方案可能对未来的演进缺乏灵活性。无论如何,函数式编程不是我目前最好的技能,所以很难判断。

StephaneM 提出的解决方案是一个很好的解决方案,但据我所知,它需要修改,不能一概而论。

@Eugene 和 @mickaël-b 在 cmets 中建议使用 Strategy Design Pattern。这是我最终选择的解决方案。谢谢大家的回答和cmets,非常有帮助:)

请参阅下面的工作代码,如果我能以任何方式使其变得更好,请随时发表评论。或者,如果我犯了一些错误...再次,我只使用 2 个类,所以为了简洁起见,这里有 2 个策略,但最终我将有超过 10 个策略。

对于那些感兴趣的人,此代码可用于从 csv 文件生成 Realm 数据库。

RealmDbGenerator dbGenerator = new RealmDbGenerator(this);    

    //flags
    dbGenerator.setGenerationStrategy(new FlagDefStrategy());
    dbGenerator.addData(FILE_FLAGS,SEPARATOR);

    //pictures
    dbGenerator.setGenerationStrategy(new PictureStrategy());
    dbGenerator.addData(FILE_PICTURES,SEPARATOR);

RealDbGenerator :

public class RealmDbGenerator {
    private static final String TAG = RealmDbGenerator.class.getSimpleName();

    public void setGenerationStrategy(GenerationStrategy generationStrategy) {
        this.generationStrategy = generationStrategy;
    }

    private GenerationStrategy generationStrategy;
    private Realm realm;
    private Context context;

    public RealmDbGenerator(Context context) {
        this.realm = Realm.getDefaultInstance();
        this.context = context;
    }

    public void addData(String csvFile, char separator) {
        CSVReader reader;

        int lineNumber = 1;

        try {
                reader = new CSVReader(new InputStreamReader(context.getAssets().open(csvFile)), separator);
                String[] line;

                realm.beginTransaction();
                while ((line = reader.readNext()) != null) {
                    generationStrategy.addData(line, realm);
                }
                realm.commitTransaction();

                reader.close();
        } catch (FileNotFoundException ex) {
            Log.e(TAG, String.format("File %s not found : %s", csvFile, ex));
        } catch (IOException ex) {
            Log.e(TAG, String.format("Error parsing line number %s : %s", lineNumber, ex));
        }
    }
}

策略界面:

public interface GenerationStrategy {
    void addData(String[] line, Realm realm);
}

FlagDefStrategy :

public class FlagDefStrategy implements GenerationStrategy {
    private static final String TAG = FlagDefStrategy.class.getSimpleName();

    @Override
    public void addData(String[] line, Realm realm) {
        FlagDef flagDef = new FlagDef(Long.parseLong(line[0]), line[1], line[2]);
        Log.d(TAG, String.format("%s %s %s", line[0], line[1], line[2]));
        realm.copyToRealm(flagDef);
    }
}

图片策略:

public class PictureStrategy implements GenerationStrategy  {
    private static final String TAG = FlagDefStrategy.class.getSimpleName();

    @Override
    public void addData(String[] line, Realm realm) {
        Picture picture = new Picture(Long.parseLong(line[0]), line[1]);
        Log.d(TAG, String.format("%s %s", line[0], line[1]));
        realm.copyToRealm(picture);
    }
}

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 2019-01-20
    • 2020-12-10
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2016-10-05
    • 2018-09-27
    相关资源
    最近更新 更多