Julian替代属性继承是什么?

时间:2017-03-07 06:00:52

标签: types abstract-class julia abstract

在许多编程语言中,父类可以要求任何子类包含特定字段。

如果该字段是静态的,则可以通过以下方式在Julia中实现相同的效果。

julia> abstract Fruit

julia> type Apple <: Fruit end

julia> type Orange <: Fruit end

julia> type Banana <: Fruit end

julia> color(::Apple) = :red
color (generic function with 1 method)

julia> color(::Orange) = :orange
color (generic function with 2 methods)

julia> color(::Banana) = :yellow
color (generic function with 3 methods)

但是,如果该字段是动态的,则不起作用。在以下示例中,我希望Pet的任何子类型都包含字段name

julia> abstract Pet

julia> type Cat <: Pet
           name::String
           hairless::Bool
       end

julia> type Dog <: Pet
           name::String
       end

julia> abstract Bird <: Pet

julia> type Parrot <: Bird
           name::String
           color::Symbol
       end

julia> type Conure <: Bird
           name::String
       end

julia> feet(::Cat) = 4
feet (generic function with 1 method)

julia> feet(::Dog) = 4
feet (generic function with 2 methods)

julia> feet(::Bird) = 2
feet (generic function with 3 methods)

do 类型必须不同,因为它们可能还有其他属性,并且可以为每种类型唯一定义方法。

  • 如何执行此要求?理想情况下,我不必在任何子类型中指定name字段,但如果必须,那就这样吧。
  • 如果朱莉娅不可能这样做,建议的替代方案是什么?我可以重构这种类型的代码以完全消除这种行为吗?

1 个答案:

答案 0 :(得分:5)

如何执行?

进行测试

using Base.Test 
@testset "Field Contract" begin
    for sub in subtypes(Pet)
        @test fieldtype(sub, :name) == String 
        #will error if no field names string.
        #will fail if it is has wrong type
    end
end

如何在子类型

上自动填写

技术上可以通过元编程实现这一点。 我推荐它。 这只是概念的黑客证明:

macro declare_abstract(typename, fields...)
    quote
        abstract $(esc(typename))
        const $(esc(Symbol(:__abfields_,typename)))  = $(esc(fields))
        $(esc(typename))
    end
end

error("Please don't use this in real code") #prevent trivial copy paste

macro declare(type_expr)
    @assert type_expr.head == :type
    @assert type_expr.args[2].head == :(<:)
    parent_typename::Symbol = type_expr.args[2].args[2]
    if isdefined(Symbol(:__abfields_, parent_typename))
        @assert type_expr.args[3].head == :block
        abfields = eval(Symbol(:__abfields_, parent_typename)) #Read a globel constant. Iffy practice right here -- using eval in a macro

        push!(type_expr.args[3].args, abfields[1].args...)
    end
    type_expr
end

用法示例:

@declare_abstract Pet (name::String), (owner_id::Int)
@declare type Cat <: Pet
end
检查它:

?Cat
...

Summary:
    type Cat <: Pet
Fields:
    name     :: String
    owner_id :: Int64

等待?为什么你说不这样做?

这是一种代码味道。 不应该完成取决于Abstract类型的字段。 朱莉娅还没有足够的基础来制定这些惯例, 标准解决方案。 我在这里谈论自己的经历。

如果您的方法将Abstract类型作为参数。 那么它应该依赖于实现类型的方法; 但不是在实现类型的字段上。 这也适用于Informal Interfaces

当您发现需要没有此字段的子类型时,它会使您的代码不灵活。 它发生在我身上。 我想我知道所有的子类型,但后来我意识到我想把别人的东西包装成一个子类型,而这个字段对此毫无意义。我所有的代码都打破了

我不是在这里进行构图与继承辩论。虽然这是相关的。

实施例

例如, 如果您需要处理不符合您假设的宠物,该怎么办? 也许是你包装牲畜包装的东西。 这些宠物实际上是Bees 它们没有名称字段,因为它们没有名称。 他们有hive字段,但它是Int

如果你正在使用方法,你会没事的: 假设你有一个get_identifier方法

对于您已准备好的类型: get_identifier(x::Union{Dog,Cat})=x.name

然后介绍与Bee类型的兼容性 你只需添加get_identifier(x::Bee)="Member of hive# $(x.hive)"。 并且您的所有代码都有效。

另一方面,如果您的代码中到处都有x.name,那么它将全部中断。 当然,您可以向构造函数添加一些内容,自动设置名称字段。 (当我发现自己处于这种情况时,就是我所做的)。 但这是一个黑客和维护负担。 当然这个例子很小。

异常

当然,这条规则有例外: 当你真的知道所有子类型的字段时。 例如,如果我编写一个解决特定问题族的数学解算器。我有AbstractResults的类型,我解决的每种类型的问题都有自己的具体子类型,以存储这种结果的特殊因素;然后我知道我家里只有5种可能的问题。 所以我知道AbstractResults只有5个具体的子类型, 我知道他们都有我给他们的实施;没有其他领域有意义。 (如果它都是非常简单的类型可行)。 那没关系。 只是不要错。

测试

您也可以使用某些测试代码检查方法

using Base.Test 
@testset "Method Contract" begin
    for sub in subtypes(Pet)
        @test method_exists(get_identifier, (sub,))
    end
end

这种“与测试签订合同”是一种动态语言模式。

结论

取决于方法更正确。 方法定义功能。 字段只是一个实现细节。 作为抽象类型的定义者,更重要的是使用抽象类型的方法,您非常清楚地了解每个子类型必须具有的功能。 你不太确定实施。

如果您的方法将Abstract类型的字段作为参数, 考虑是否以正确的方式编写