struct的继承问题


#1

有struct的继承需求的时候要怎么处理?

struct A
    a::Int
end
struct B <: A  # 怎么继承
    b::int
end

#2

和传统OOP不同,Julia里不允许concrete type的继承,请参考Composition_over_inheritance的设计原则, 不要滥用继承。


#3

还是有点困惑,如果类B和C都拥有一个共同部分A的成员变量,但是有个函数(或者说接口),对A,B,C有同样的操作,就是改变A中某个变量的值,那该如何设计?
大概草稿如下(伪代码)

struct A
    a::Int
end

struct B <: A
    b::Int
end

struct C <: A
    c::Int
end


function f!(obj_a::A)
    obj_a.a = 2
end

#4

composition:

abstract type AbstractA end
abstract type AbstractBC <: AbstractA end
mutable struct A <: AbstractA
    a::Int 
end
mutable struct B <: AbstractBC
    a::A
    b::Int
end
mutable struct C <: AbstractBC
   a::A
   c::Int
end

f!(x::AbstractA) = x.a = 2 
f!(x::AbstractBC) = f!(x.a)
   
julia> aa = A(1)
A(1)

julia> bb = B(A(1), 2)
B(A(1), 2)

julia> cc = C(A(1), 3)
C(A(1), 3)

julia> f!(aa); aa
A(2)

julia> f!(bb); bb
B(A(2), 2)

julia> f!(cc); cc
C(A(2), 3)


补充阅读材料(材料中有可能Julia的版本较老,但不影响领悟到其中的意思):

https://medium.com/@Jernfrost/defining-custom-units-in-julia-and-python-513c34a4c971


#5

Roger 也写过一篇简短的专栏,可以作为参考:https://zhuanlan.zhihu.com/p/38434080


#7

这样确实可行,但是我现在的父类有十几个成员,这样我就要对每个成员写两个函数来对A和B、C公共的成员做设置?


#8

假设:你这个OO本身设计的没有问题

你提到的情况是Composition_over_inheritance设计准则的drawback, 解决方案是用 trait-based dispatch, https://github.com/mauro3/SimpleTraits.jl

通过你提供的已有信息,我猜你在用Julia移植一些非科学计算相关的代码,注意Julia主要目标用户是做科学计算的,要做通用编程,传统OO最好还是用经典的OO语言。这里不是说Julia不能做,而是做起来要改变设计思路,比较困难。

假设:你这个OO本身设计的就是有问题的

在这种情况下,需要贴出具体代码,并详细解释实际的问题,预期的结果和当前的解决方案,以便大家讨论最佳实现。


#9

我确实是写了非科学计算的代码,因为这些代码以前是python写的,但是现在遇到了性能瓶颈。用C++重写的代价比较大,所以采用Julia来尝试写


#10

能给一些更多的信息吗? 还是代码需要保密?


#11

抱歉,这是公司的代码,我不能拿出来。这个程序大概是一个模拟器,模拟某种物体的运动,这个物体有非常多的属性,也有一些种类(通过继承实现),对于这个物体有公共的动作,但是每个动作对各个种类也有自己额外的操作

class Obj:
    def __init__(self):
        self.xxx = xxx
        ...
    def f(self):
        # actions
        ...

class ObjA(Obj):
    def __init__(self):
        super(ObjA, self).__init__()
        self.yyy = yyy
        ...
    def f(self):
        super(ObjA, self).f()
        # other action
        ...

#12

大体明白了,Obj里有很多公共属性,然后想用overriding。这样把公共的属性放到Obj中,子类继承AbstractObj即可:

julia> abstract type AbstractObj end

julia> mutable struct Obj
           a::Int
       end

julia> mutable struct A <: AbstractObj
           b::Obj
           c::Int
       end

julia> mutable struct B <: AbstractObj
          b::Obj
          d::Int
       end

julia> f!(x::AbstractObj) = x.b.a = 20  # default
f! (generic function with 1 method)

julia> f!(x::A) = x.b.a = 30
f! (generic function with 2 methods)

julia> xx = Obj(1)
Obj(1)

julia> aa = A(Obj(1), 2)
A(Obj(1), 2)

julia> bb = B(Obj(1), 3)
B(Obj(1), 3)

julia> f!(aa); aa
A(Obj(30), 2)

julia> f!(bb); bb
B(Obj(20), 3)

如果是物体的种类是树形结构,就用abstract type多分几层。


#13

如果我想在外部访问公共部分的成员变量,有什么方式嘛?


#14

正常访问即可:

julia> aa.b.a
1

如果想模拟公共成员变量就在A中,可以重载Base.getproperty:

julia> function Base.getproperty(x::AbstractObj, name::Symbol)
           if name in fieldnames(Obj) # this assumes all subtypes have a common filed b::Obj
               return getfield(x.b, name)
           else
               return getfield(x, name)
           end
       end

julia> aa = A(Obj(1), 2)
A(Obj(1), 2)

julia> aa.a
1

# 重载以便自动补全
julia> function Base.propertynames(x::AbstractObj, private::Bool=false)
           public = fieldnames(typeof(x))
           true ? ((public ∪ fieldnames(Obj))...,) : public
       end

julia> aa.
a b  c


#15

多谢多谢,问题基本解决了。
不过这样来看其实完全可以模拟出struct继承的