【问题标题】:Design pattern name: Object of ClassA acts like a "class" for objects of ClassB设计模式名称: 类对象就像 B 类对象的“类”
【发布时间】:2018-12-11 12:53:00
【问题描述】:

我有两个班级*,我不确定如何称呼它们之间的关系。 ClassA 的对象有点像 ClassB 的对象的类(“原型”或“模式”)。

  • Song 有一个标题和艺术家。
  • 一个SongDimension (ClassA) 有一个标签(例如“流派”;“响度”)和一组AcceptableValue 的(例如“经典摇滚”、“古典”、 “雷鬼”;“9”、“10”、“11”)。
  • 一个Song 有一组SongDimensions,并且对于每个SongDimension,它只有一个SongDimensionValueClassB),它告诉AcceptableValue 中的哪一个对于那个SongDimension 适用于这个Song

我正在尝试使用此模式搜索最佳实践(或替代方案,如果这不是一个好的模式),但我不知道该怎么称呼它。在我看来,SongDimension 的对象几乎就像SongDimensionValue 的对象的类。但是,当我使用类似的术语在 Internet 上搜索时,我只会得到关于什么是类和对象的基本文章。

这种模式有名字吗?

* 我使用的是 Java 术语(类、对象),但这种模式可以应用于任何 OOP 语言。

编辑:感谢大家的回答。阅读它们,我意识到我忘了提到一个重要的要求。我希望用户能够在运行时创建自己的 SongDimension 实例,而我在编译时无法预测。

例如,系统的一位用户可能不关心使用流派SongDimension 对他们库中的Songs 进行分类。相反,他们使用标签“颜色”创建自己的SongDimension,并为其赋予可接受的值,如红色、黄色和黑色。这个新的SongDimension 可能对其他用户没有任何意义,但对他们有意义,这就是我想要允许的那种灵活性。他们仍然可以使用响度SongDimension,而无需使用流派。

如果没有这个要求,枚举和继承确实是要走的路。这是我一开始就不包括它的坏处。

【问题讨论】:

  • 我想我明白你的意思......但是如果你有任何代码sn-ps,你能给我们一些代码

标签: oop design-patterns


【解决方案1】:

这听起来像是power type。也许尝试这样的事情:

import java.util.Arrays;
import java.util.LinkedHashMap;
import java.util.LinkedHashSet;
import java.util.Map;
import java.util.Set;
class Artist {}
class Song {
    Song(String title,Artist artist,Map<Dimensions,Object> dimensionToValue) {
        this.title=title;
        this.artist=artist;
        this.dimensionToValue=dimensionToValue;
    }
    void setDimensionValue(Dimensions dimension,Object object) {
        try {
            if(dimensionToValue.containsKey(dimension)) {
                if(dimension.dimensionValues.contains(object)) {
                    System.out.println("set "+dimension+" to "+object);
                    dimensionToValue.put(dimension,object);
                }
                else throw new RuntimeException(object+" is illegal!");
            } else throw new RuntimeException(dimension+"is illegal dimension! for this song?");
        } catch(Exception e) {
            System.out.println("caught: "+e);
        }
    }
    Object getDimensionValue(Dimensions dimension) {
        return dimensionToValue.get(dimension);
    }
    String title;
    Artist artist;
    final Map<Dimensions,Object> dimensionToValue;
}
class Data {
    static final String[] genreValuesArray=new String[] {"country","rock"};
    static final Integer[] loudnessValuesArray=new Integer[] {1,2,3};
    static final Set<Object> genreValues=new LinkedHashSet(Arrays.asList(genreValuesArray));
    static final Set<Object> loudnessValues=new LinkedHashSet(Arrays.asList(loudnessValuesArray));
}
enum Dimensions {
    genre(Data.genreValues),loudness(Data.loudnessValues);
    Dimensions(Set<Object> dimensionValues) {
        this.dimensionValues=dimensionValues;
    }
    final Set<Object> dimensionValues;
}
public class So53724633_design_pattern_name_object_of_classa_acts_like_a_class_for_objects_of_classb {
    public static void main(String[] args) {
        Map<Dimensions,Object> map=new LinkedHashMap<>();
        Dimensions dimension=Dimensions.genre;
        map.put(dimension,null);
        Song song1=new Song("foo",new Artist(),map);
        Object value=song1.getDimensionValue(dimension);
        System.out.println("value of "+dimension+" is: "+value);
        song1.setDimensionValue(Dimensions.genre,Data.genreValuesArray[0]);
        value=song1.getDimensionValue(dimension);
        System.out.println("value of "+dimension+" is: "+value);
        song1.setDimensionValue(Dimensions.genre,"classical");
        song1.setDimensionValue(Dimensions.loudness,Integer.valueOf(1));
    }
}

【讨论】:

  • 您能否将此与我之前的答案进行比较,让我知道您看到了什么优势?
  • 一个对比是我创建的类更少。因为维度枚举被重用,因为每个只有一个实例。
  • 电源类型正是我想要的!阅读您链接的文章,我发现了其他有用的搜索词,例如“类型对象”、“元对象”、“分区集”和“clabject”。但是,我可能弄错了,但我不确定您发布的代码是否真的说明了该模式,因为电源类型应该在运行时定义,而您使用的静态数组和枚举是在编译时烘焙的,不是吗? ?
  • 枚举是内置的。所有维度值都是在运行时生成的,或者可以从数据库中获取。我的代码没有说明模式,但可能是等价的,可以做你想做的。
  • 幂类型也称为类型对象()。 cs.sjsu.edu/~pearce/modules/patterns/analysis/top.htm sorta 上的“动态对象模型(属性列表)”可能很有趣。
【解决方案2】:

听起来您正在寻找一个枚举。

SongDimension 是一个接口。 GenreLoudness等是实现接口的枚举类。可接受的值被定义为各个枚举的实例。

这很符合您的描述:SongDimension 的具体实现是枚举类,其实例对象定义了可接受值的范围。 Java 特别适合这样的设计,因为它的枚举类是一等公民,像任何其他类一样支持状态和行为。

这不是设计模式,但它是 Java 中常见的编码习惯。

public class Main {
    public static void main(String... args) {
        Song mySong = new Song("My Favorite Song", "My Favorite Band",
                               Genre.Classic_Rock, Loudness.Eleven);
        System.out.println(mySong);
    }

    static class Song {
        final String title;
        final String artist;
        final Collection<SongDimension> dimensions = new HashSet<>();

        Song(String title, String artist, SongDimension... dimensions) {
            this.title = title;
            this.artist = artist;
            this.dimensions.addAll(Arrays.asList(dimensions));
        }
        @Override public String toString() {
            return "Song{ title=" + title + ", artist=" + artist +
                       ", dimensions=" + dimensions + " }";
        }
    }

    interface SongDimension {
        String label();
        String value();
    }

    enum Genre implements SongDimension {
        Classic_Rock, Classical, Reggae;

        @Override public String label() {
            return getDeclaringClass().getSimpleName();
        }
        @Override public String value() {
            return name().replace('_',' ');
        }
        @Override public String toString() {
            return label() + ':' + value();
        }
    }

    enum Loudness implements SongDimension {
        Nine(9), Ten(10), Eleven(11);

        final int volume;
        Loudness(int volume) {
            this.volume = volume;
        }

        @Override public String label() {
            return getDeclaringClass().getSimpleName();
        }
        @Override public String value() {
            return String.valueOf(volume);
        }
        @Override public String toString() {
            return label() + ':' + value();
        }
    }
}

【讨论】:

    【解决方案3】:

    TL;DR;您似乎描述了一个常见的继承问题。有一个 SongDimension 的概念,还有其他不同的概念,它们都共享一个事实,即它们是 SongDimension 的子类型。 IE。响度是一首歌曲的一个维度,节奏也是如此,流派也是如此。查找多态性和继承性。

    现在来一个更长的答案。

    巨大的免责声明!

    请原谅任何 Java 语法错误。您似乎最精通 Java,因此即使我不是经常使用 Java 编码器,我认为最好在答案中说 Java。但一般模式适用于许多语言。

    在答案的最后,我使用了一些比我应该谈论的更高级的概念,但它可能是鼓舞人心的。


    在这种情况下,我希望尽可能保持一般性。我还希望能够施加任何有意义的限制,例如您对某些允许值的建议,以及允许的计算。

    我会这样想这个问题:

    有一个 Song 课。每首歌曲都是该类的一个实例。

    song 类有一个 SongDimension 类型的对象集合:

    public class Song {
        public string title;
        public string artist;
        public List<SongDimension> songDimensions;
    }
    

    SongDimension 类是具有以下签名的抽象类

    public abstract class SongDimension {
        public abstract string GetValue();
    }
    

    然后对于每种类型的维度,您可能希望为可接受的值生成枚举,或者应用自定义逻辑,例如响度的情况,如下所示:

    public enum Genres {
        ClassicRock, Classical, Reggae
    }
    
    
    public class Genre extends SongDimension {
        private Genres _genre;
    
        public string GetValue(){
            switch(_genre) {
               case ClassicRock: return "Classic Rock";
               default: return _genre.name();
            }
        }
    
        public Genre(Genres whichGenre) {
            _genre = whichGenre;
        }
    }
    
    public class Loudness extends SongDimension {
        private int _loudness;
    
        public string GetValue(){
            return String.valueOf(_loudness);
        }
    
        public int GetIntValue() {
            return _loudness;
        }
    
        public Loudness(int howLoud) {
            if(howLoud < 9 || howLoud > 11) throw new IllegalArgumentException();
    
            _loudness = howLoud;
        }
    
        // Add more relevant operations on the value here,
        // maybe you can take differences of loudnesses for instance?
    }
    

    要使用它,你可以像这样创作几首歌曲:

    Song EineKleineNachtMusik = new Song();
    EineKleineNachtMusik.songDimensions.Add(new Genre(Genres.Classical));
    EineKleineNachtMusik.songDimensions.Add(new Loudness(9));
    
    Song BackInBlack = new Song();
    BackInBlack.songDimensions.Add(new Genre(Genres.ClassicRock));
    BackInBlack.songDimensions.Add(new Loudness(11));
    
    for(SongDimensions d : EineKleineNachtMusik.songDimensions){
        System.out.println(d.GetValue());
    }
    /// prints:
    /// Classical
    /// 9
    

    所有这一切的最大价值在于,它让您拥有关于歌曲的各种信息,而不是作为字符串,而是作为适当的对象。对值和类型使用字符串占位符的风险在于,您最终可能会对例如“11”的真正含义产生不同的解释。即使您可能只能将值“9”、“10”或“11”指定为“响度”,但没有什么能阻止您稍后将“10”错误地视为完全不同的东西,例如歌曲长度(分钟)。

    通过这种方式,我们可以创建语义合理的对象,并将自定义逻辑整合到不同类型的特征中。例如,我们可以让 Loudness 类实现 Comparable&lt;Loudness&gt;(肯定是幼稚的实现。例如不检查 null):

    public class Loudness extends SongDimension implements Comparable<Loudness> {
        ...
        // Same as before with the addition of the following:
        @Override public int compareTo(Loudness that) {
            if(_loudness < that.GetIntValue()) return -1;
            if(_loudness == that.GetIntValue()) return 0;
            if(_loudness > that.GetIntValue()) return 1;
        }  
    }
    

    鉴于我不是 Java 开发人员,现在我将面临一些非常薄的冰。这很可能包含错误,所以请随意在 cmets 中 bash 所有你想要的东西。但这进一步说明了通过使用一些最小样板,一个人可以同时获得许多好处,为维度强制执行适当的值,实现自定义适当的逻辑,对每种维度类型都不同等。

    Song 类中添加了一个小辅助方法来检索特定类型的维度:

     public class Song {
        ...
        // same as above, with the addition of:
    
        public <T extends SongDimension> T GetDimension(Class<T> dimType) {
           return songDimensions.stream().filter(dim -> dim instanceof 
                dimType).Collect(Collectors.toList()).iterator().next()
        }
    }
    

    然后我们可以像这样比较两首歌曲:

    Loudness l1 = EineKleineNachtMusik.GetDimension(Loudness.class);
    Loudness l2 = BackInBlack.GetDimension(Loudness.class);
    
    if(l1 != null && l2 != null && l1.compareTo(l2) < 0) {
       System.out.println("Yup, BackInBlack is the louder of the two");
    }
    

    【讨论】:

    • 这似乎与我之前的答案基本相同,只是更冗长。
    • 是的,我开始回答,然后离开了一段时间,当我回来点击提交时,你已经回答了。我想过把它拿走,但我认为它有一些关于为什么正确的类型而不是简单的枚举有用的观点。流派与响度不是很相似,因此应该对它们进行不同的建模。他们唯一的共同点是他们是一首歌曲可以拥有的东西。对那些具有唯一行为的模型中的任何一个进行建模,即它们可以是什么类型似乎确实受到限制。响度可以作为放大器的参数,而流派则不能
    • @jaco0646,你不同意吗?
    • 也许应该使用装饰器模式或其他东西。我的意思是,响度的主要内容可能不是 SongDimension。它首先是对声压的主观测量。它可能有一个单位与价值。任何声音都可以有响度,而不仅仅是歌曲。也许可以在需要时使用装饰器模式将各种类转换为 SongDimensions。 Duration 当然是 SongDimension。当 Java 已经实现了这个类时,不需要为此创建一个与字符串值配对的哑枚举。改为使其适合 SongDimension 方案。
    【解决方案4】:

    我尝试提供一个不需要任何Enum 的解决方案,因为您正在寻找:

    我使用的是 Java 术语(类、对象),但这种模式可以应用于任何 OOP 语言。

    可能是逻辑错误?

    也许给定的问题包含一个逻辑错误:

    一首歌有一组 SongDimensions
    [...]
    SongDimension (ClassA) 有一个标签(例如“流派”;“响度”)

    这意味着一个Song-object 可以同时具有多种不同的流派和不同的响度。

    但如果你在代码中思考,你真正想说的是new Song("mj", "billie jean", new Rock(), new LoudnessLevel8())

    但是在您给定的问题中,Billie jean 可能是 reggae 的响度 6,同时它是 摇滚响度8

    如果我错了,请在 cmets 中纠正我。

    没有逻辑错误的解决方案

    歌曲

    一首歌有一个标题和艺术家。一首歌曲有一个Set SongDimensions。

    class Song {
      private String title;
      private String artist;
      private SongDimension songDimension;
    }
    

    歌曲维度

    对于SongDimension,我们需要GenreLoudness。对于这些,我们可以创建一个抽象的而不是多个具体的类型。这里以Genre 为例:

    interface Genre { /**/ }
    
    class Classic implements Genre { /**/ }
    
    class Rock implements Genre { /**/ }
    

    这些具体类型(如 ClassicRock)是您的 AcceptableValue

    让我们尝试创作一首新歌:

    SongDimension rockBy8= new SongDimension(new Rock(), new LoudnessLevel8());
    Song billieJean = new Song("mj", "billie jean", rockBy8);
    

    SongDimensionValue

    我认为不需要SongDimensionValue.. 在 cmets 中纠正我

    【讨论】:

    • 您似乎将SetList 混淆了。 Set 是 unique 元素的集合。它不能“同时拥有多种不同的流派和不同的响度”。一个集合最多可以包含一个SongDimension。另请注意,枚举在各种语言中都很常见。 Java 恰好比大多数更强大。但它们绝不是不合常规的。
    • “一个 Set 最多可以包含每个 SongDimension 中的一个” -> 这是我的句子。我对 List 和 Set 并不感到困惑。对我来说,一首歌有多个 SongDimension 是没有意义的
    • 你对平等提出了很好的观点。然而,多对一关系仍然是必要的,因为用户可以选择他们使用哪个SongDimensions 来描述Songs。例如,Song 可以有流派SongDimension,但不能有响度,反之亦然。我认为带有SongDimension 键和SongDimensionValues 作为值的MapSet 更合适
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2012-10-31
    • 2016-08-25
    • 1970-01-01
    • 2011-08-02
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多