java设计模式

什么是设计模式

设计模式(Design pattern)是一套被反复使用、多数人知晓的、经过分类编目的、代码设计经验的总结。使用设计模式是为了可重用代码、让代码更容易被他人理解、保证代码可靠性。 毫无疑问,设计模式于己于他人于系统都是多赢的,设计模式使代码编制真正工程化,设计模式是软件工程的基石,如同大厦的一块块砖石一样。项目中合理的运用设计模式可以完美的解决很多问题,每种模式在现在中都有相应的原理来与之对应,每一个模式描述了一个在我们周围不断重复发生的问题,以及该问题的核心解决方案,这也是它能被广泛应用的原因

设计模式是在软件设计中反复出现的问题的通用解决方案。它们是经过多次验证和应用的指导原则,旨在帮助软件开发人员解决特定类型的问题,提高代码的可维护性、可扩展性和重用性。

设计模式是一种抽象化的思维方式,可以帮助开发人员更好地组织和设计他们的代码。它们提供了一种通用的框架,可以用于解决各种不同的软件设计问题。设计模式不是完整的代码,而是一种描述问题和解决方案之间关系的模板。

设计模式并不是一成不变的法则,而是根据不同的问题和情境来决定是否使用以及如何使用。了解和应用设计模式可以帮助开发人员更好地组织代码,提高代码的可读性和可维护性,同时也有助于促进团队之间的合作和沟通。

单例模式

问题:

在某些情况下,需要确保一个类只有一个实例,并且需要一个全局访问点来访问这个实例。例如,在一个应用程序中,一个配置管理器类需要保持一致的配置数据,以避免不同部分之间的配置冲突。

解决方案:

单例模式通过确保一个类只能创建一个实例,并提供一个静态方法或静态属性来访问这个实例。通常,单例类会将自己的构造函数声明为私有,以防止外部代码直接创建实例。通过一个静态方法,单例类可以控制在运行时只能获得同一个实例。

效果:

单例模式的应用可以确保在整个应用程序中只有一个实例存在,从而节省了资源和内存。它也可以提供一个全局的访问点,使得代码中的各个部分都可以方便地获取这个实例。然而,过度使用单例模式可能导致全局状态的难以控制,以及模块之间的紧耦合。在多线程环境下需要小心处理,以确保线程安全。

总之,单例模式是一种常用的设计模式,适用于需要全局唯一实例的场景。它的核心思想在于通过限制类的实例化来控制对象的数量,从而保证全局唯一性。

保证一个类仅有一个实例,并提供一个访问它的全局访问点。

单个实例,一个程序中,该类的对象只允许产生一个。

  1. 外界不能随便new(构造方法私有化)
  2. 提供公共的获取对象方式静态的
  3. new 的动作只能一次!!!静态变量随着类的加载 初始化,初始化一次

饿汉式

只要类加载,对象就产生,无论是否使用

多线程使用没有问题

package com.it.single;

/*
        如何完成单例

        1:私有构造方法
        2:定义静态变量  完成初始化
        3:提供公共的静态的获取方法

      饿汉式:
         只要类加载了,这个对象就产生了。
         不管别人用不用。

      多线程使用没有问题。
 */
public class Single {

	 // 饿汉式有可能会浪费内存

    // 这四组数据非常耗内存资源,饿汉式上来就把所有的资源全部加载进内存中了
    // 但是这四组数据的空间并没有被使用
    // 可能会浪费空间
    private byte[] data1 = new byte[1024*1024];
    private byte[] data2 = new byte[1024*1024];
    private byte[] data3 = new byte[1024*1024];
    private byte[] data4 = new byte[1024*1024];

    //定义静态变量
    private final static Single single= new Single();

//    私有了构造,别人就无法new这个对象了
    private Single(){}

    //提供公共的访问方式  静态的
    public static Single newInstance(){

        return single;
    }
}

懒汉式

package com.it.single2;
/*

    饿汉式
        类一加载就创建了对象。

        浪费内存。
        延迟加载。用的时候再创建。

     懒汉式
        延迟对象的产生
 */
public class Single {
    //定义静态变量
    private static Single single = null;

//    私有了构造
    private Single(){}

    //提供公共的访问方式  静态的
    public  static Single newInstance(){

        //   判断该对象是否已经创建了
        if(single==null){
            single = new Single();
        }
        return single;
    }
}

线程安全的懒汉式

package com.it.single3;

public class Single {
    //定义静态变量
    private volatile static Single single ;

//    私有了构造
    private Single(){}

    //提供公共的访问方式  静态的
    public static Single newInstance(){

       
       //为了提高效率加一个 非空判断
       if(single==null){
           synchronized (Single.class){
               //   判断该对象是否已经创建了
               if(single==null){
                   single = new Single();
               }
           }
       }

        return single;
    }
}

非要双重检查锁定吗?直接加锁不也能实现线程安全么?

只有在single对象为null的情况下才会进入同步块,这是为了确保只有一个线程能够创建实例。但是, 如果single对象已经被创建,每次调用getInstance()方法时都会进行同步操作,这会导致性能下降

静态内部类——更好的解决方案

public class Singleton{

	private static class InnerSingleton{
	
		private static final Singleton singleton = new Singleton();
	}
	
	
	private Singleton(){}
	public static final Singleton newInstance(){
	
		return InnerSingleton.singleton;
	}
}

这种静态内部类实现单例模式的方式是线程安全的并且同时保证了性能,主要有以下几个原因:

  1. 类加载机制:静态内部类在被调用前不会进行加载,也就是说,只有在newInstance()方法被调用时才会加载InnerSingleton类。在类加载的过程中,JVM会保证线程安全,确保只有一个线程能够完成类的加载并初始化InnerSingleton.singleton对象。

  2. final关键字:在静态内部类InnerSingleton中,我们使用了final关键字修饰singleton对象。final关键字保证了该对象的引用不可变,即一旦InnerSingleton.singleton被赋值,就无法再被修改。这样可以防止其他线程对singleton对象进行修改或重新赋值。

  3. 唯一的实例:由于InnerSingleton.singleton是静态的且final的,它只会被初始化一次。在整个程序执行过程中,无论有多少线程调用newInstance()方法,只会返回同一个singleton对象。

单例和多例的使用场景

  • 当整个应用只允许出现一个类实例时,我们经常用到单例模式。比如工具类,国际化服务提供类等等
  • 多并发请求环境下,系统需要为每个客户端的独立请求提供单独服务的资源,但是系统总的开销是有限的,系统在并发量很大时也不可能为所有的并发请求同时提供相应的资源,否则不但系统资源消耗量大而且非常耗时。这时就可以考虑使用池的概念,也即是一种多例模式的实现。具体的应用场景,比如数据库连接池、EJB无状态会话Bean的实例池

工厂模式

问题:

在软件设计中,我们经常遇到需要创建不同类型对象的情况。但是,如果直接在代码中实例化对象,会使代码紧密耦合在一起,难以维护和扩展。此外,如果对象的创建方式需要变化,那么就需要在整个代码中进行大量的修改。工厂方法模式旨在解决这个问题。

解决方案:

工厂方法模式提供了一个创建对象的接口,但是将具体的对象创建延迟到子类中。这样,客户端代码不需要知道要创建的具体对象的类,只需要通过工厂方法来创建对象。这使得客户端代码与具体对象的创建解耦,提高了代码的灵活性和可维护性。

在工厂方法模式中,通常会定义一个抽象工厂类,其中包含一个创建对象的抽象方法,而具体的对象创建则由具体的子类实现。这样,每个具体的子类都可以根据需要创建不同类型的对象,而客户端代码只需要通过抽象工厂类来调用工厂方法,而不需要关心具体的对象创建细节。

效果:

工厂方法模式的优点包括:

  • 松耦合:客户端代码与具体对象的创建解耦,使得系统更具弹性和可维护性。
  • 扩展性:通过添加新的具体工厂和产品子类,可以很容易地扩展系统以支持新的对象类型。
  • 封装性:将对象的创建集中在工厂类中,封装了对象的创建细节,使得客户端代码更简洁。

然而,工厂方法模式也可能引入一些额外的复杂性,因为需要定义多个工厂类和产品类的层次结构。这可能会导致系统中类的数量增加。在选择使用工厂方法模式时,需要根据具体情况进行权衡。

工厂方法模式在实际应用中非常常见,例如,图形库可以使用工厂方法模式来创建不同类型的图形对象,数据库访问框架可以使用工厂方法模式来创建不同类型的数据库连接等。

定义一个用于创建对象的接口,让子类决定将哪一个类实例化。工厂模式使一个类的实例化延迟到其子类。

在这里插入图片描述

abstract class Animal {
    public abstract void sound();
}
class Cat extends Animal {
    @Override
    public void sound() {
        System.out.println("喵喵喵");
    }
}
class Dog extends Animal {
    @Override
    public void sound() {
        System.out.println("汪汪汪");
    }
}
// 创建一个工厂类
class AnimalFactory {
    // 定义一个静态方法,根据传入的参数创建具体的产品类对象
    public static Animal createAnimal(String type) {
        if (type.equalsIgnoreCase("dog")) {
            return new Dog();
        } else if (type.equalsIgnoreCase("cat")) {
            return new Cat();
        } else {
            throw new IllegalArgumentException("Invalid animal type: " + type);
        }
    }
}
// 客户端代码
public class Main {
    public static void main(String[] args) {
        // 使用工厂类创建不同的 Animal 对象
        Animal dog = AnimalFactory.createAnimal("dog");
        dog.sound();
        Animal cat = AnimalFactory.createAnimal("cat");
        cat.sound();
    }
}

抽象工厂模式

提供一个创建一系列相关或互相依赖对象的接口,而无需指定它们具体的类。

通过定义一个创建对象的接口来创建对象,但将具体实现的决定留给子类来决定。
在抽象工厂模式中,接口是负责创建一个相关对象的工厂,不需要显式指定它们的类。每个生成的工厂都能按照工厂模式提供对象。

在这里插入图片描述

// 创建一个抽象产品类
abstract class Animal {
    public abstract void sound();
}
class Cat extends Animal {
    @Override
    public void sound() {
        System.out.println("喵喵喵");
    }
}
// 创建具体产品类,继承自 Animal 类
class Dog extends Animal {
    @Override
    public void sound() {
        System.out.println("汪汪汪");
    }
}

abstract class AnimalFactory {
    // 定义一个抽象方法,用于创建 Animal 对象
    public abstract Animal createAnimal();
}
class CatFactory extends AnimalFactory {
    @Override
    public Animal createAnimal() {
        return new Cat();
    }
}
// 创建具体工厂类,实现创建 Animal 对象的接口
class DogFactory extends AnimalFactory {
    @Override
    public Animal createAnimal() {
        return new Dog();
    }
}
// 客户端代码
public class Main {
    public static void main(String[] args) {
        // 创建一个 Dog 对象
        AnimalFactory dogFactory = new DogFactory();
        Animal dog = dogFactory.createAnimal();
        dog.sound();

        // 创建一个 Cat 对象
        AnimalFactory catFactory = new CatFactory();
        Animal cat = catFactory.createAnimal();
        cat.sound();
    }
}

应用场景

  • 当需要创建对象的过程比较复杂,需要进行复杂的初始化或依赖注入时,可以使用工厂模式来封装这个过程,简化客户端代码。
  • 当需要创建多个对象具有相似功能或共享一些相同属性时,可以使用工厂模式来创建这些对象,从而使代码更加简洁和易于维护。

需要注意的是,虽然工厂模式可以提高代码的灵活性和可维护性,但是如果工厂的创建过程非常复杂,可能会影响系统的性能。此时需要权衡设计的复杂度和系统的性能要求,选择合适的创建方式。

装饰器模式(Decorator Pattern)

问题:

在某些情况下,我们需要在不修改现有对象结构的情况下,动态地添加功能或责任。继承在这种情况下可能会导致类爆炸问题,而且修改现有类可能会影响到其他部分的代码。

解决方案:

装饰模式提供了一种在运行时动态地为对象添加新功能的方法,通过创建一个装饰类来包装原始类。装饰类具有与原始类相同的接口,它内部包含一个指向原始对象的引用,并且可以根据需要包装额外的功能。这样,你可以通过组合不同的装饰类来构建出具有不同功能组合的对象。

效果:

装饰模式的优点包括避免了类爆炸问题,因为你可以通过组合少量的装饰类来实现各种功能组合。它也使得功能的增加和修改更加灵活,不会影响到其他部分的代码。然而,装饰模式可能会导致增加很多小型的类,从而增加了代码的复杂性。

在装饰模式中,通常涉及以下角色:

  1. 组件(Component):定义了一个抽象的接口,可以是具体对象或装饰器所共有的接口。
  2. 具体组件(Concrete Component):实现了组件接口,是被装饰的原始对象。
  3. 装饰器(Decorator):持有一个指向组件对象的引用,并实现了组件的接口。它可以包含额外的功能,也可以将请求传递给组件对象。
  4. 具体装饰器(Concrete Decorator):扩展了装饰器类,通过添加额外的功能来装饰具体组件。

通过这种方式,装饰模式允许你将功能嵌套地堆叠在一起,以实现各种不同的功能组合,同时保持代码的灵活性和可维护性。


动态的给一个对象添加一些额外的功能。就扩展功能而言,装饰者模式比生成子类方式更为灵活。

装饰器模式是一种结构性设计模式,它允许您在不影响同一类的其他对象的行为的情况下,静态或动态地向单个对象添加行为。 当您想要在运行时添加或删除对象的功能时,或者当您想要减少创建不同行为组合所需的子类数量时,此模式非常有用。

在Java中,使用继承和组合的结合来实现装饰器模式。 具体来说,您需要创建一个基类或接口来定义对象的核心行为,然后创建一个或多个装饰器类来向对象添加附加行为。 每个装饰器类都具有对其装饰的对象的引用,并且它可以在委托给对象的原始行为之前或之后修改对象的行为。

装饰器模式适用于以下场景:

  1. 在不修改现有代码的情况下,向现有类添加新的功能。
  2. 在运行时动态地向对象添加新的行为。
  3. 以不同的方式组合对象,以实现不同的行为。

使用装饰器模式时需要注意以下几点:

  1. 装饰器类需要实现与被装饰对象相同的接口,以便可以对被装饰对象进行包装。
  2. 装饰器类应该在调用被装饰对象的方法之前或之后添加新的行为。
  3. 不要创建过多的装饰器对象,否则会导致代码变得复杂难以维护。

在这里插入图片描述

public interface Pizza {
    public String getDescription();
    public double getCost();
}
 // 具体组件
public class PlainPizza implements Pizza {
    public String getDescription() {
        return "薄饼";
    }
    public double getCost() {
        return 4.00;
    }
}
 // 装饰器
public abstract class ToppingDecorator implements Pizza {
    protected Pizza pizza;
    public ToppingDecorator(Pizza pizza) {
        this.pizza = pizza;
    }
    public String getDescription() {
        return pizza.getDescription();
    }
    public double getCost() {
        return pizza.getCost();
    }
}
 // 具体装饰器
public class Cheese extends ToppingDecorator {
    public Cheese(Pizza pizza) {
        super(pizza);
    }
    public String getDescription() {
        return pizza.getDescription() + ",马苏里拉奶酪";
    }
    public double getCost() {
        return pizza.getCost() + 0.50;
    }
}
 // 具体装饰器
public class Pepperoni extends ToppingDecorator {
    public Pepperoni(Pizza pizza) {
        super(pizza);
    }
    public String getDescription() {
        return pizza.getDescription() + ",意大利辣香肠";
    }
    public double getCost() {
        return pizza.getCost() + 1.00;
    }
}
 // 客户端代码
public class PizzaShop {
    public static void main(String[] args) {
        Pizza pizza = new PlainPizza();
        pizza = new Cheese(pizza);
        pizza = new Pepperoni(pizza);
        System.out.println(pizza.getDescription());
        System.out.println("成本:$" + pizza.getCost());
    }
}

代理模式(Proxy Pattern)

问题:

在某些情况下,我们希望通过一个中间代理来控制对某个对象的访问。这可能是因为原始对象的创建或访问涉及复杂的逻辑,或者我们想要在访问原始对象之前或之后执行一些操作。

解决方案:

代理模式提供了一个代理对象,它充当了原始对象的替代品,以控制对原始对象的访问。代理对象与原始对象实现相同的接口,使得客户端可以无缝地切换和使用。代理对象可以对客户端的请求进行拦截、修改或增强,然后将请求传递给原始对象。


为其他对象提供一个代理以控制对这个对象的访问。

代理模式是项目中常用的一种设计模式。提供了间接访问目标对象的一种方式;即通过代理对象访问目标对象。
这样做的好处是,可以在不改变原有目标对象的基础上,对目标对象增加额外的扩展功能。
代理模式又分为静态代理、jdk动态代理、cglib动态代理三种实现方式。

三种实现方式各有优点,以及适用的场景:

  • 静态代理:代理类必须非常明确,所以无法做到通用,但是效率也是最高的
  • jdk动态代理:必须基于接口代理,有一定局限性;动态生成字节码文件,可以用于通用业务(性能日志等)
  • cglig动态代理:也是动态生成字节码文件,生成的代理类继承了目标对象
  • spring aop默认代理策略是:如果目标对象实现了接口,则使用jdk动态代理,否则使用cglib代理
  • jdk8之后,jdk动态代理效率要高于cglib代理

静态代理

被代理对象与代理对象需要实现相同的接口或者是继承相同父类,因此要定义一个接口或抽象类。

/**代理接口*/
public interface IHello {
    String hi(String key);
}
/**代理接口实现类*/
public class HelloImpl implements IHello {
    @Override
    public String hi(String key) {
        String str = "hello:" + key;
        System.out.println("HelloImpl! " + str);
        return str;
    }
}
/**静态代理类*/
public class HelloStaticProxy implements IHello {

    private IHello hello;

    public HelloStaticProxy(IHello hello) {
        this.hello = hello;
    }

    @Override
    public String hi(String key) {
        System.out.println(">>> static proxy start");
        String result = hello.hi(key);
        System.out.println(">>> static proxy end");
        return result;
    }
}
/**测试*/
public class DemoTest {

    public static void main(String[] args) {
        IHello helloProxy = new HelloStaticProxy(new HelloImpl());
        helloProxy.hi("world");
    }
}

jdk动态代理

jdk动态代理是基于接口的一种代理方式,目标对象一定要实现接口。

原理是,利用反射机制,动态生成匿名类继承Proxy类并且实现了要代理的接口,由于java不支持多继承,所以JDK动态代理不能代理类

/**代理接口*/
public interface IHello {
    String hi(String key);
}
/**代理接口实现类*/
public class HelloImpl implements IHello {
    @Override
    public String hi(String key) {
        String str = "hello:" + key;
        System.out.println("HelloImpl! " + str);
        return str;
    }
}


/**jdk动态代理类*/
public class JdkProxy implements InvocationHandler {

    private Object target;

    public JdkProxy(Object target) {
        this.target = target;
    }

    /**
     * 获取被代理接口实例对象
     *
     * @param <T>
     * @return
     */
    public <T> T getProxy() {
        return (T) Proxy.newProxyInstance(target.getClass().getClassLoader(), target.getClass().getInterfaces(), this);
    }

    @Override
    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
        System.out.println(">>> JdkProxy start");
        Object result = method.invoke(target, args);
        System.out.println(">>> JdkProxy end");
        return result;
    }
}

/**测试*/
public class Demo2Test {

    public static void main(String[] args) {
        JdkProxy proxy = new JdkProxy(new HelloImpl());
        IHello helloProxy = proxy.getProxy();
        helloProxy.hi(" jdk proxy !");
    }
}

装饰器模式和代理模式的区别
装饰器模式强调的是: 增强、新增行为 ;代理模式强调的是: 对代理的对象(代理类)施加控制,但不对对象本身的功能进行增强 。

项目中怎么使用设计模式的?

工厂模式

  1. 简单工厂模式是属于创建型模式,又叫做静态工厂方法模式。
    它的实现方式是由一个工厂类根据传入的参数,动态决定应该创建哪一个产品类(这些产品类继承自一个父类或接口)的实例。

  2. 简单工厂模式由三种角色组成:
    (1)工厂角色:简单工厂模式的核心,它负责实现创建所有实例的内部逻辑。工厂类的创建产品类的方法可以被外界直接调用,创建所需的产品对象。
    (2)抽象产品角色:简单工厂模式所创建的所有对象的父类,它负责描述所有实例所共有的公共接口。
    (3)具体产品角色:是简单工厂模式的创建目标,所有创建的对象都是充当这个角色的某个具体类的实例。

  3. 场景:在一个商城系统中,用户可以选择三种支付方式,微信支付,支付宝支付,余额支付。

  4. 实现:

我们将创建一个 PaymentMethod 接口和实现 PaymentMethod 接口的实体类。下一步是定义工厂类 PayFactory。

Main是演示我们简单工厂模式的入口。

UML图如下:
在这里插入图片描述

 步骤1.创建接口类

public interface PaymentMethod {
    void pay();
}
步骤2.实现接口类

public class Alipay implements PaymentMethod {
    @Override
    public void pay() {
        System.out.println("支付宝支付");
    }
}
public class BalancePay implements PaymentMethod {
    @Override
    public void pay() {
        System.out.println("余额支付");
    }
}
public class WechatPay implements PaymentMethod {
    @Override
    public void pay() {
        System.out.println("微信支付");
    }
}
步骤3.创建一个工厂,生成基于给定信息的实体类的对象。

public class PayFactory {

    public PaymentMethod getPayment(String payType){

        switch (payType){

            case "alipay":
                return new Alipay();
            case "wechatPay":
                return new WechatPay();
            case "balancePay":
                return new BalancePay();

            default:
                System.out.println("支付方式错误");

        }
        return null;

    }

}
步骤4.使用该工厂,通过传递类型信息来获取实体类的对象。

public class Main {

    public static void main(String[] args) {

        PayFactory payFactory = new PayFactory();

        PaymentMethod paymentMethod = payFactory.getPayment("alipay");

        paymentMethod.pay();

    }

}

装饰者模式

比如为商品添加不同的属性或功能,如包装、赠品等。

interface ProductComponent {
    double getPrice();
}

class BasicProduct implements ProductComponent {
    @Override
    public double getPrice() {
        return 50.0;
    }
}

class GiftDecorator extends ProductComponent {
    private ProductComponent component;

    public GiftDecorator(ProductComponent component) {
        this.component = component;
    }

    @Override
    public double getPrice() {
        return component.getPrice() + 10.0;
    }
}

SpringBoot常用设计模式

在 Spring Boot 中,常用的设计模式有以下几种:

  1. 单例模式
    Spring 容器中的 Bean 默认都是单例模式。单例模式确保一个类只有一个实例,并提供一个全局访问点。这样可以减少对象创建的开销,并且方便在整个应用程序中共享状态。
    例如,在 Spring Boot 中配置的各种服务类通常都是单例的,在整个应用程序的生命周期中只创建一次实例。

  2. 工厂模式
    Spring 的 BeanFactory 和 ApplicationContext 都是工厂模式的体现。它们负责创建和管理各种 Bean 对象。通过配置文件或注解,Spring 容器可以根据需求创建不同类型的对象,将对象的创建和使用分离。
    比如,当你在一个业务类中需要使用数据库访问对象时,不需要自己去实例化,而是通过依赖注入从 Spring 容器中获取已经创建好的数据库访问对象实例。

  3. 代理模式
    Spring AOP(面向切面编程)中广泛使用了代理模式。通过代理模式,可以在不修改目标对象代码的情况下,为目标对象添加额外的功能,如事务管理、日志记录、性能监控等。
    例如,当一个方法被标注为需要事务管理时,Spring 会创建一个代理对象,在调用目标方法之前开启事务,在方法执行完成后提交或回滚事务。

  4. 观察者模式
    Spring 的事件机制基于观察者模式。当一个特定的事件发生时,多个观察者可以收到通知并进行相应的处理。
    比如,在用户注册成功后,可以触发一个用户注册事件,其他模块可以监听这个事件并进行一些后续的操作,如发送欢迎邮件、更新用户统计信息等。

  5. 模板方法模式
    Spring 的 JdbcTemplate 是模板方法模式的一个例子。它定义了数据库操作的基本流程,如获取连接、执行 SQL 语句、处理结果集等,而具体的 SQL 语句和结果处理逻辑由使用者提供。
    这样可以在不重复编写数据库连接和事务管理等通用代码的情况下,方便地进行不同的数据库操作。

部分内容转载自:
https://www.bilibili.com/video/BV1V4411p7EF/?p=10&spm_id_from=pageDriver&vd_source=64c73c596c59837e620fed47fa27ada7
https://blog.csdn.net/wlddhj/article/details/131071730
https://zhuanlan.zhihu.com/p/651451595
https://blog.csdn.net/miles067/article/details/132073141

在这之后过了大约30万年,物质和能量开始形成复杂的结构,称为原子,再进一步构成分子。至于这些原子和分子的故事以及它们如何互动,就成了化学。

人类简史

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值