Har*_*din 3 abstract-class types abstract julia
在许多编程语言中,父类可以要求任何子类包含特定字段.
如果该字段是静态的,则可以通过以下方式在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)
Run Code Online (Sandbox Code Playgroud)
但是,如果该字段是动态的,则不起作用.在以下示例中,我想要求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)
Run Code Online (Sandbox Code Playgroud)
类型都必须是不同的,因为它们可以具有其他属性,以及,和方法可为每种类型的被唯一地限定.
name在任何子类型中指定字段,但如果必须,那就这样吧.通过测试
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
Run Code Online (Sandbox Code Playgroud)
在元编程方面,技术上可以做到这一点.我不推荐它.这只是概念的黑客证明:
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
Run Code Online (Sandbox Code Playgroud)
用法示例:
@declare_abstract Pet (name::String), (owner_id::Int)
@declare type Cat <: Pet
end
Run Code Online (Sandbox Code Playgroud)
核实:
?Cat
...
Summary:
type Cat <: Pet
Fields:
name :: String
owner_id :: Int64
Run Code Online (Sandbox Code Playgroud)
这是一种代码味道.不应该完成取决于Abstract类型的字段. Julia还不够完善,无法通过标准解决方案制定这些惯例.我在这里谈谈自己的经历.
如果您的方法将Abstract类型作为参数.那么它应该依赖于实现类型的方法; 但不是在实现类型的字段上.这也适用于非正式接口.
当您发现需要没有此字段的子类型时,它会使您的代码不灵活.它发生在我身上.我想我知道所有的子类型,但后来我意识到我想把别人的东西包装成一个子类型,而这个字段对此毫无意义.我所有的代码都破了
我不是在这里进行构图与继承辩论.虽然这是相关的.
例如,如果您需要处理不符合您假设的宠物,该怎么办?也许是你包装牲畜包装的东西.这些宠物实际上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种可能的问题.所以我知道只有5个具体的子类型AbstractResults,我知道它们都有我给它们的实现; 没有其他领域有意义.(如果它都是非常简单的类型可行).那没关系.只是不要错.
您也可以使用一些测试代码来检查方法
using Base.Test
@testset "Method Contract" begin
for sub in subtypes(Pet)
@test method_exists(get_identifier, (sub,))
end
end
Run Code Online (Sandbox Code Playgroud)
这种"与测试签订合同"是一种动态语言模式.
取决于方法更正确.方法定义功能.字段只是一个实现细节.作为抽象类型的定义者,更重要的是使用抽象类型的方法,您非常清楚地了解每个子类型必须具有的功能.您不太确定实施.
如果您的方法将Abstract类型的字段作为参数,请考虑它是否以正确的方式编写