显示意图和隐式的区别
隐性和显性这两个术语在您初次听到时都会习惯一些。 当您在编程方面听到他们的声音时,这对您意味着什么? 一种方法比另一种更好吗? 在这里,我们将逐个讨论这些想法,并提供一些示例,这些示例可能会带来一些设计模式上的好处。
条款
在编程中,隐式常用于指代幕后其他代码为您完成的工作。 显式是通过明确编写要完成的指示来完成所需更改的手动方法。 在较小的图中,隐式和显式通常是用于将类型强制转换为所需类型的术语。 从更大的角度看,您可能正在谈论约定而不是配置,其中约定是代码库或框架为您隐式完成的工作,而配置只是显式设置。
根据所使用的编程语言以及该语言是静态类型还是动态类型 ,使用哪种方法的适当性或益处也存在差异。 它还取决于是否可以从运行时或编译期间推断事物。 由于这些因素,仅在狭窄的上下文中声明一种方法要优于另一种方法是正确的,因为您必须考虑所涉及的编程语言和程序的设计。
C中的隐式和显式类型转换的示例如下:
int implicit;
implicit = 4.5;
int explicit;
explicit = (int)4.5;
在这里, implicit
和explicit
变量名被定义为int
类型。 一旦给定值4.5
,隐式版本将使编译器将通常为float或double类型的值转换为整数,而显式版本通过使用(int)
强制将其转换为整数,将显式类型转换为整数。
静态类型的语言
在诸如Rust之类的静态类型语言中,绝大多数值的创建和赋值都将具有显式的类型注释要求,但编译器可以在何处推断类型除外。 以下是显示Rust中显式和隐式类型的示例。
fn add_one(input: u8) -> u8 {
input + 1
}
let four = add_one(3);
在这里,方法add_one
是关于输入类型和输出类型的显式方法。 此处添加的数字1
在编译时会隐式地设为u8
数字,因为上下文有助于推断类型,并且u8
加法仅实现为与u8
数字一起使用。 由于方法本身定义了将返回的类型,因此最后一行隐式地键入为u8
。
通过使用泛型,Rust可以推断出什么。 但是推断的使用仅限于在编译时就可以知道的东西。 如果在编译时无法识别它,则必须在分配的任何点显式定义类型。 采取以下方法定义:
use std::ops::Add;
fn add_both<t: Add>(a: T, b: T) -> T::Output {
a + b
}</t:>
这里T
可以是实现特征Add
任何类型。 T::Output
类型是在为您的特定类型定义Add特质时定义的,在这种情况下,通常与T
本身具有相同的类型。 现在,如果我们提供两个数字作为参数,则编译器将推断出其类型。
let x = add_both(3 , 4 ); // implicit type
let y: u8 = add_both(3u8 , 4u8 ); // explicit type
let z: u32 = add_both(3u32, 4u32); // explicit type
当我运行上面的代码时, x
被推断为i32
类型。 y
和z
示例要求在提供给函数时知道输入参数的类型,因为T::Output
的推论不一定与T的推论相同。 这将默认为i32
,然后分配一个i32
的u8
或u32
是完全错误的。
动态类型语言
现在有了动态类型的语言,您就不必再为类型本身担心了,而更多地担心隐式或显式对象或行为。 围绕具有相同行为的对象进行代码建模是所谓的鸭子类型化 ,这是面向对象编程中一个较高级的思想领域,其中处理这些对象是隐式的。 而围绕特定对象类对代码进行建模非常类似于使用显式或隐式类型化。 但是,当您接受任何类型的对象作为输入时,该部分中的代码必须要么显式处理不同的对象,要么将其职责移交给其他地方。
为了澄清起见,当您编写用于处理各种事情的代码时,则在编写显式代码。 但是,如果已经编写了相同的代码,并且您通过一个简单的方法调用来重用它,则在此新上下文中的行为将被隐含。 隐式地认为事情是自动完成的,并在当前范围之外进行处理。
在Ruby中 ,大多数类型都有显式或隐式转换的设计。 这个想法是隐式转换方法旨在用于隐式上下文中,而显式转换旨在使开发人员能够在更多上下文中内联编写。 让我通过示例进行演示。
# explicit
"4".to_i + "5".to_i
# => 9
# implicit
class Seven
def to_int
7
end
end
Array.new(Seven.new)
# => [nil, nil, nil, nil, nil, nil, nil]
在这里,显式示例使读者非常清楚,我们正在将String
对象转换为Integer
对象,并在两者之间进行加法Integer
。 隐式示例对读者而言并不那么明显,因为Array.new
方法在给定的任何参数上隐式调用to_int方法。 Integer类具有在其每个实例上定义的to_int方法,该方法仅返回self。 如果您写42.to_int
您只需返回42
。 通过隐式转换将其用作方法调用的输入保护的这种用途,是一种设计使类型安全的好方法。 如果将错误的对象类型定义为未定义to_int
输入,则会发生这种情况。
Array.new("32")
# TypeError (no implicit conversion of String into Integer)
它不仅失败,而且还向我们提供了一条有用的信息,即尝试进行隐式转换,并告诉给定的对象类和期望的对象类。 Ruby中的隐式转换方法是表达该对象确实是您所期望的一种方式。 显式转换只是将其转换为预期的类型。
Ruby对许多核心对象都有隐式和显式选项。
Array#to_a // explicit
Array#to_ary // implicit
Hash#to_h // explicit
Hash#to_hash // implicit
Kernel#to_s // explicit
String#to_i // explicit
String#to_str // implicit
Integer#to_i // explicit
Integer#to_s // explicit
Integer#to_int // implicit
Ruby还有一些带有类方法的类,这些类将执行隐式转换或返回nil,并且该方法的名称为try_convert
。
Array.try_convert // calls `to_ary` on the parameter or returns `nil`
Hash.try_convert // calls `to_hash` on the parameter or returns `nil`
String.try_convert // class `to_str` on the parameter or returns `nil`
我们可以通过Array.new
来遵循Ruby的示例,并通过为自己的自定义类型设计隐式转换来很好地保护输入参数的种类。 在此示例中,由于to_f
是Ruby中对Float数的显式转换,我们将使用as_
作为前缀而不是to_
。 这是隐式作为安全模式的基本示例。
class Foo
def as_f
self
end
def as_foo
self
end
end
class Bar
def initialize(foo)
begin
@foo = foo.as_foo
rescue NoMethodError
raise TypeError, "no implicit conversion of #{foo.class} into Bar"
end
end
end
Bar.new(4)
# Traceback (most recent call last):
# ...
# TypeError (no implicit conversion of Integer into Bar)
Bar.new(Foo.new)
# => #<bar:0x00005600711d10d8 @foo=#<Foo:0x00005600711d1100>></bar:0x00005600711d10d8>
现在,这将帮助使用Bar
类的其他开发人员不要将其作为不兼容的参数传递。 它遵循Ruby语言中的约定,应该使开发人员更容易理解,但会有更多有用的错误。 当您有其他想要转换为Foo对象的对象时,可以在其上定义as_f
方法以进行显式转换,并且使用该新对象的开发人员将在其使用Bar.new(Baz.new.as_f)
。 这样可以确保代码可以在Bar
和Foo
已经工作的范围内正常工作。
摘要
隐式和显式编码以及隐式和显式代码的行为是通过执行其他行为或类型设置/广播的上下文来定义的。 具体来说,隐式或显式方法由要使用它们的上下文定义。 如果事物的名称合理,则隐式代码将是非常不错的体验,因为它可使事物保持简单。
但是,隐式代码,即代码在后台为您服务的代码,如果做错了,也可能很难解决。 显式代码使您在看代码时就很清楚,因为您已经在先完成了要执行的操作的详细信息,并且不必在其他地方跟踪问题,但是通常需要做很多工作才能编写代码。 就其本身而言,它可能会变得势不可挡,因此在两者之间找到适当的平衡通常是最佳的解决方案。 当必须理解时,必须明确,而当设计和命名概念容易理解时,则应隐式。 这将提供更方便的开发经验。
翻译自: https://www.javacodegeeks.com/2018/11/difference-implicit-explicit-programming.html
显示意图和隐式的区别